forked from FoundKeyGang/FoundKey
add API route for OAuth access token retrieval
This commit is contained in:
parent
a13e956af0
commit
7db7fdd9e2
3 changed files with 103 additions and 0 deletions
94
packages/backend/src/server/api/common/oauth.ts
Normal file
94
packages/backend/src/server/api/common/oauth.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import Koa from 'koa';
|
||||||
|
import { IsNull, Not } from 'typeorm';
|
||||||
|
import { Apps, AuthSessions, AccessTokens } from '@/models/index.js';
|
||||||
|
import config from '@/config/index.js';
|
||||||
|
|
||||||
|
export async function oauth(ctx: Koa.Context): void {
|
||||||
|
const {
|
||||||
|
grant_type,
|
||||||
|
code,
|
||||||
|
// TODO: check redirect_uri
|
||||||
|
// since this is also not checked in the legacy app authentication
|
||||||
|
// it seems pointless to check it here, and it is also not stored.
|
||||||
|
redirect_uri,
|
||||||
|
} = ctx.request.body;
|
||||||
|
|
||||||
|
// check if any of the parameters are null or empty string
|
||||||
|
if ([grant_type, code].some(x => !x)) {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = {
|
||||||
|
error: 'invalid_request',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grant_type !== 'authorization_code') {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = {
|
||||||
|
error: 'unsupported_grant_type',
|
||||||
|
error_description: 'only authorization_code grants are supported',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authHeader = ctx.headers.authorization;
|
||||||
|
if (!authHeader?.toLowerCase().startsWith('basic ')) {
|
||||||
|
ctx.response.status = 401;
|
||||||
|
ctx.response.set('WWW-Authenticate', 'Basic');
|
||||||
|
ctx.response.body = {
|
||||||
|
error: 'invalid_client',
|
||||||
|
error_description: 'HTTP Basic Authentication required',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [client_id, client_secret] = new Buffer(authHeader.slice(6), 'base64')
|
||||||
|
.toString('ascii')
|
||||||
|
.split(':', 2);
|
||||||
|
|
||||||
|
const [app, session] = await Promise.all([
|
||||||
|
Apps.findOneBy({
|
||||||
|
id: client_id,
|
||||||
|
secret: client_secret,
|
||||||
|
}),
|
||||||
|
AuthSessions.findOneBy({
|
||||||
|
appId: client_id,
|
||||||
|
token: code,
|
||||||
|
// only check for approved auth sessions
|
||||||
|
userId: Not(IsNull()),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
if (app == null) {
|
||||||
|
ctx.response.status = 401;
|
||||||
|
ctx.response.set('WWW-Authenticate', 'Basic');
|
||||||
|
ctx.response.body = {
|
||||||
|
error: 'invalid_client',
|
||||||
|
error_description: 'authentication failed',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (session == null) {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = {
|
||||||
|
error: 'invalid_grant',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ token ] = await Promise.all([
|
||||||
|
AccessTokens.findOneByOrFail({
|
||||||
|
appId: client_id,
|
||||||
|
userId: session.userId,
|
||||||
|
}),
|
||||||
|
// session is single use
|
||||||
|
AuthSessions.delete(session.id),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.body = {
|
||||||
|
access_token: token.token,
|
||||||
|
token_type: 'bearer',
|
||||||
|
// FIXME: per-token permissions
|
||||||
|
scope: app.permission.join(' '),
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
/*
|
||||||
|
This route is already in use, but the functionality is provided
|
||||||
|
by '@/server/api/common/oauth.ts'. The route is not here because
|
||||||
|
that route requires more deep level access to HTTP data.
|
||||||
|
*/
|
|
@ -15,6 +15,7 @@ import { handler } from './api-handler.js';
|
||||||
import signup from './private/signup.js';
|
import signup from './private/signup.js';
|
||||||
import signin from './private/signin.js';
|
import signin from './private/signin.js';
|
||||||
import signupPending from './private/signup-pending.js';
|
import signupPending from './private/signup-pending.js';
|
||||||
|
import { oauth } from './common/oauth.js';
|
||||||
import discord from './service/discord.js';
|
import discord from './service/discord.js';
|
||||||
import github from './service/github.js';
|
import github from './service/github.js';
|
||||||
import twitter from './service/twitter.js';
|
import twitter from './service/twitter.js';
|
||||||
|
@ -74,6 +75,9 @@ for (const endpoint of endpoints) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the OAuth endpoint does some shenanigans and can not use the normal API handler
|
||||||
|
router.post('/auth/session/oauth', oauth);
|
||||||
|
|
||||||
router.post('/signup', signup);
|
router.post('/signup', signup);
|
||||||
router.post('/signin', signin);
|
router.post('/signin', signin);
|
||||||
router.post('/signup-pending', signupPending);
|
router.post('/signup-pending', signupPending);
|
||||||
|
|
Loading…
Reference in a new issue