89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
import axios, {AxiosResponse} from "axios";
|
|
import {
|
|
BaseEntity,
|
|
|
|
Column,
|
|
Entity,
|
|
PrimaryGeneratedColumn
|
|
} from "typeorm";
|
|
import {URLSearchParams} from "url";
|
|
import {Config} from "../config/Config";
|
|
import {API_URL, OAUTH_SCOPE} from "../config/Constants";
|
|
import {DiscordToken} from "./DiscordToken";
|
|
|
|
/**
|
|
* StateToken is an OAuth2 state token.
|
|
*
|
|
* These are stored in the database and used to prevent attacks on the OAuth2
|
|
* flow, stored persistently in order of server restart, and pruned.
|
|
*
|
|
* The verify method also checks the validity of the token, and completes the OAuth2 flow.
|
|
* The constructor automatically sets the expiration time for tokens, of 30
|
|
* minutes, in case the OAuth2 flow is not completed.
|
|
*
|
|
*/
|
|
@Entity({ name: "state_tokens" })
|
|
export class StateToken extends BaseEntity {
|
|
@PrimaryGeneratedColumn("uuid")
|
|
id: string;
|
|
|
|
@Column({ unique: true })
|
|
token: string;
|
|
|
|
@Column()
|
|
expiring: Date;
|
|
|
|
constructor(token: string) {
|
|
super();
|
|
|
|
if (token === undefined) {
|
|
return;
|
|
}
|
|
|
|
this.token = token;
|
|
this.expiring = new Date();
|
|
this.expiring.setMinutes(this.expiring.getMinutes() + 30);
|
|
}
|
|
|
|
async verify({ api }: Config, code: string): Promise<DiscordToken> {
|
|
const params = new URLSearchParams();
|
|
params.append("client_id", api.client_id);
|
|
params.append("client_secret", api.client_secret);
|
|
params.append("grant_type", "authorization_code");
|
|
params.append("code", `${code}`);
|
|
params.append("redirect_uri", `${api.redirect_uri}`);
|
|
params.append("scope", OAUTH_SCOPE);
|
|
|
|
let r: AxiosResponse;
|
|
try {
|
|
r = await axios.post(`${API_URL}/oauth2/token`, params.toString(), {
|
|
validateStatus: (status) => status < 500,
|
|
});
|
|
} catch (err) {
|
|
console.error("Could not decode request data: ", this, err);
|
|
Promise.reject({
|
|
reason: "An internal error has occurred.",
|
|
status: 500,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (r.status != 200) {
|
|
console.error("Bad request from discord", r);
|
|
Promise.reject({ reason: r.data["error_description"], status: 400 });
|
|
return;
|
|
}
|
|
|
|
const authToken = r.data["access_token"];
|
|
|
|
let dToken = await DiscordToken.findOne({ where: { authToken } });
|
|
if (!dToken) {
|
|
dToken = new DiscordToken(this.token, authToken);
|
|
dToken.save();
|
|
}
|
|
|
|
this.remove();
|
|
return dToken;
|
|
}
|
|
}
|