add API route for OAuth access token retrieval

This commit is contained in:
Johann150 2022-10-16 01:11:47 +02:00 committed by Gitea
parent a13e956af0
commit 7db7fdd9e2
3 changed files with 103 additions and 0 deletions

View 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(' '),
};
};

View file

@ -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.
*/

View file

@ -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);