Added routes for handling 3rd party OAuth.

This commit is contained in:
Tom
2025-04-02 20:05:34 +00:00
parent 56a8c1a66a
commit 13fb846277

View File

@ -205,12 +205,12 @@ app.post('/api/keys', apiMiddlewares, async (req: any, res: any, next: any) => {
const userId = req.user.impersonation?.id ?? req.user.id;
const keys = await db.one('SELECT count(*) FROM "ApiKey" WHERE "userId" = $1', userId);
if (keys.count > 10) {
res.status(400).send('too many keys');
res.status(403).send('Too many keys');
return;
}
const label = req.body.label;
if (!label) {
res.status(400).send('no label is attached.');
res.status(400).send('No label is attached.');
return;
}
const key = uuidv4();
@ -228,6 +228,8 @@ app.delete('/api/keys', apiMiddlewares, async (req: any, res: any, next: any) =>
res.status(400).send('key does not exist.');
return;
}
await db.none('DELETE FROM "ApiKey" WHERE id = $1', req.body.key);
res.send({ key: req.body.key });
});
@ -243,7 +245,7 @@ app.get('/api/twitch/redemptions', apiMiddlewares, async (req: any, res: any, ne
const twitch = JSON.parse(await resp.readBody());
if (!twitch?.data) {
console.log('Failed to fetch twitch data:', account, twitch?.data);
console.log('Failed to fetch twitch data:', account, twitch.data);
res.status(401).send({ error: 'Could not fetch Twitch channel redemption data.' });
return;
}
@ -252,9 +254,9 @@ app.get('/api/twitch/redemptions', apiMiddlewares, async (req: any, res: any, ne
});
app.get("/api/auth/twitch/users", apiMiddlewares, async (req: any, res: any) => {
const username = req.query.login.toLowerCase();
const username = req.query.login?.toLowerCase();
if (!username) {
res.send({ user: null });
res.status(400).send({ user: null });
return;
}
@ -269,7 +271,7 @@ app.get("/api/auth/twitch/users", apiMiddlewares, async (req: any, res: any) =>
});
const twitch = JSON.parse(await resp.readBody());
if (!twitch?.data) {
res.send({ user: null });
res.status(403).send({ user: null });
return;
}
@ -277,6 +279,100 @@ app.get("/api/auth/twitch/users", apiMiddlewares, async (req: any, res: any) =>
res.send({ user });
});
app.post("/api/auth/connections", apiMiddlewares, async (req: any, res: any) => {
const name = req.body.name;
const type = req.body.type?.toLowerCase();
const client_id = req.body.client_id;
const grant_type = req.body.grant_type?.toLowerCase();
if (!name || !type || !client_id || !grant_type) {
const missing = [name, type, client_id, grant_type]
res.status(400).send({ error: 'Missing fields in the body.' });
return;
}
const AuthData: { [service: string]: { type: string, endpoint: string, grantType: string, scopes: string[], redirect: string } } = {
'nightbot': {
type: 'nightbot',
endpoint: 'https://api.nightbot.tv/oauth2/authorize',
grantType: 'token',
scopes: ['song_requests', 'song_requests_queue', 'song_requests_playlist'],
redirect: 'https://beta.tomtospeech.com/connections/callback'
},
'twitch': {
type: 'twitch',
endpoint: 'https://id.twitch.tv/oauth2/authorize',
grantType: 'token',
scopes: [
'chat:read',
'bits:read',
'channel:read:polls',
'channel:read:predictions',
'channel:read:subscriptions',
'channel:read:vips',
'moderator:read:blocked_terms',
'chat:read',
'channel:moderate',
'channel:read:redemptions',
'channel:manage:redemptions',
'channel:manage:predictions',
'user:read:chat',
'channel:bot',
'moderator:read:followers',
'channel:read:ads',
'moderator:read:chatters',
],
redirect: 'https://beta.tomtospeech.com/connections/callback'
},
};
const url = AuthData[type].endpoint;
const redirect = AuthData[type].redirect;
const scopes = AuthData[type].scopes.join(' ');
const nounce = uuidv4();
await db.none('INSERT INTO "ConnectionState" ("name", "type", "clientId", "grantType", "state", "userId") VALUES ($1, $2, $3, $4, $5, $6)'
+ ' ON CONFLICT ("userId", "name") DO UPDATE SET "type" = $2, "clientId" = $3, "grantType" = $4, "state" = $5;',
[name, type, client_id, grant_type, nounce, req.user.id]);
const redirect_uri = url + '?client_id=' + client_id + '&force_verify=true&redirect_uri=' + redirect + '&response_type=token&scope=' + scopes + '&state=' + nounce;
res.send({ success: true, error: null, data: redirect_uri });
});
app.get("/api/auth/connections", async (req: Request, res: Response) => {
const state = req.query['state'];
const access_token = req.query['token'];
let expires_in = req.query['expires_in'];
if (!state || !access_token) {
res.status(400).send({ error: 'Missing fields in the body.' });
return;
}
const connection = await db.oneOrNone('SELECT "name", "type", "clientId", "grantType", "userId" FROM "ConnectionState" WHERE "state" = $1', [state]);
if (!connection) {
res.status(400).send({ error: 'Failed to link the account.' });
return;
}
if (connection.type == 'twitch') {
const rest = new httpm.HttpClient(null);
const response = await rest.get('https://id.twitch.tv/oauth2/validate', {
'Authorization': 'OAuth ' + access_token,
});
const json = JSON.parse(await response.readBody());
expires_in = json.expires_in;
}
if (!expires_in) {
res.status(400).send({ error: 'Could not determine the expiration of the token.' });
return;
}
const expires_at = new Date();
expires_at.setSeconds(expires_at.getSeconds() + parseInt(expires_in.toString()) - 300);
res.send({
data: { connection, expires_at },
});
});
app.post("/api/auth/twitch/callback", async (req: any, res: any) => {
const query = `client_id=${process.env.AUTH_CLIENT_ID}&client_secret=${process.env.AUTH_CLIENT_SECRET}&code=${req.body.code}&grant_type=authorization_code&redirect_uri=${process.env.AUTH_REDIRECT_URI}`
const rest = new httpm.HttpClient(null);