From e851c0700dff044c6c9ea66025685edb161c08f2 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Thu, 30 Nov 2023 22:43:45 -0500 Subject: [PATCH 1/3] Allow one-time-tokens to not expire during validation. --- changelog.md | 3 +++ schemas/one-time-token-exchange.json | 27 +++++++++++++++++++ ...oken.json => one-time-token-generate.json} | 0 src/one-time-token/controller/exchange.ts | 21 ++++++++------- src/one-time-token/service.ts | 19 ++++++++----- 5 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 schemas/one-time-token-exchange.json rename schemas/{one-time-token.json => one-time-token-generate.json} (100%) diff --git a/changelog.md b/changelog.md index 6d6d5877..db4b4f8b 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,9 @@ Changelog ------------------- * Clients can now specify how long a one-time-token should be valid for. +* API clients can now request that one-time-tokens don't expire after use. +* The client_id is now validated to belong to the curent user when validating + one-time-tokens. 0.25.0 (2023-11-22) diff --git a/schemas/one-time-token-exchange.json b/schemas/one-time-token-exchange.json new file mode 100644 index 00000000..7a7c60cc --- /dev/null +++ b/schemas/one-time-token-exchange.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://curveballjs.org/schemas/a12nserver/one-time-token-exchange.json", + "type": "object", + "description": "Request body of the exchange one-time-token endpoint.", + "required": ["token","client_id"], + "additionalProperties": false, + + "properties": { + "token": { + "type": "string", + "description": "The token previously obtained with the 'generate one-time-token' endpoint." + }, + "client_id": { + "type": "string", + "description": "The OAuth2 client_id. This client will be associated with the generated token." + }, + "activateUser": { + "type": "boolean", + "description": "Activate the user if the token was valid." + }, + "dontExpire": { + "type": "boolean", + "description": "Don\'t expire the one-time-token even if it was correct." + } + } +} diff --git a/schemas/one-time-token.json b/schemas/one-time-token-generate.json similarity index 100% rename from schemas/one-time-token.json rename to schemas/one-time-token-generate.json diff --git a/src/one-time-token/controller/exchange.ts b/src/one-time-token/controller/exchange.ts index d150e50b..7b303386 100644 --- a/src/one-time-token/controller/exchange.ts +++ b/src/one-time-token/controller/exchange.ts @@ -14,24 +14,26 @@ type OtteRequest = { activateUser?: boolean; token: string; client_id: string; + dontExpire?: boolean; } class OneTimeTokenExchangeController extends Controller { - async post(ctx: Context) { + async post(ctx: Context) { ctx.privileges.require('a12n:one-time-token:exchange'); + ctx.request.validate('https://curveballjs.org/schemas/a12nserver/one-time-token-exchange.json'); + const principalService = new PrincipalService(ctx.privileges); - if (!ctx.request.body.token) { - throw new UnprocessableEntity('A token must be provided for the exchange'); - } - if (!ctx.request.body.client_id) { - throw new UnprocessableEntity('A client_id must be provided for the exchange'); + const client = await oauth2ClientService.findByClientId(ctx.request.body.client_id); + if (!ctx.privileges.isPrincipal(client.app)) { + throw new Forbidden(`The client_id ${ctx.request.body.client_id} is not associated with the currently authenticated app`); } - - const user = await tokenService.validateToken(ctx.request.body.token); - + const user = await tokenService.validateToken( + ctx.request.body.token, + ctx.request.body.dontExpire ?? false, + ); if (!user.active) { if (ctx.request.body.activateUser) { user.active = true; @@ -41,7 +43,6 @@ class OneTimeTokenExchangeController extends Controller { } } - const client = await oauth2ClientService.findByClientId(ctx.request.body.client_id); const oauth2Token = await oauth2Service.generateTokenOneTimeToken({ client, principal: user, diff --git a/src/one-time-token/service.ts b/src/one-time-token/service.ts index c8e8847f..80d6bcf2 100644 --- a/src/one-time-token/service.ts +++ b/src/one-time-token/service.ts @@ -34,17 +34,22 @@ export async function createToken(user: User, expiresIn: number | null): Promise * This function only works once for every token. * After calling this function, the token automatically gets deleted. */ -export async function validateToken(token: string): Promise { +export async function validateToken(token: string, dontExpire: boolean = false): Promise { - const query = 'SELECT token, user_id FROM reset_password_token WHERE token = ? AND expires_at > ?'; - const result = await db.raw(query, [token, Math.floor(Date.now() / 1000)]); + const result = await db('reset_password_token') + .select() + .where({token}) + .andWhere('expires_at', '>', Math.floor(Date.now() / 1000)) + .first(); - if (result[0].length !== 1) { - throw new BadRequest ('Failed to validate token'); + if (!result) { + throw new BadRequest('Failed to validate token'); } else { - await db.raw('DELETE FROM reset_password_token WHERE token = ?', [token]); + if (!dontExpire) { + await db('reset_password_token').delete().where({token}); + } const principalService = new PrincipalService('insecure'); - return principalService.findById(result[0][0].user_id) as Promise; + return principalService.findById(result.user_id, 'user'); } } From 64cac93168aad4868a354a5e1d48c1763c58db1e Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Thu, 30 Nov 2023 22:46:39 -0500 Subject: [PATCH 2/3] Remove unused import --- src/one-time-token/controller/exchange.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/one-time-token/controller/exchange.ts b/src/one-time-token/controller/exchange.ts index 7b303386..067b1136 100644 --- a/src/one-time-token/controller/exchange.ts +++ b/src/one-time-token/controller/exchange.ts @@ -1,7 +1,7 @@ import Controller from '@curveball/controller'; import { Context } from '@curveball/core'; -import { Forbidden, UnprocessableEntity } from '@curveball/http-errors'; +import { Forbidden } from '@curveball/http-errors'; import { tokenResponse } from '../../oauth2/formats/json'; From eb87c0fe705fcf804c663392d204ae63edddb4f7 Mon Sep 17 00:00:00 2001 From: Evert Pot Date: Thu, 30 Nov 2023 22:50:12 -0500 Subject: [PATCH 3/3] Badly escaped character --- schemas/one-time-token-exchange.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/one-time-token-exchange.json b/schemas/one-time-token-exchange.json index 7a7c60cc..232afbfb 100644 --- a/schemas/one-time-token-exchange.json +++ b/schemas/one-time-token-exchange.json @@ -21,7 +21,7 @@ }, "dontExpire": { "type": "boolean", - "description": "Don\'t expire the one-time-token even if it was correct." + "description": "Don't expire the one-time-token even if it was correct." } } }