diff --git a/changelog.md b/changelog.md index f0db3ac6..6d6d5877 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,12 @@ Changelog ========= +0.25.1 (????-??-??) +------------------- + +* Clients can now specify how long a one-time-token should be valid for. + + 0.25.0 (2023-11-22) ------------------- diff --git a/schemas/one-time-token.json b/schemas/one-time-token.json new file mode 100644 index 00000000..6c3efb0b --- /dev/null +++ b/schemas/one-time-token.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://curveballjs.org/schemas/a12nserver/one-time-token-generate.json", + "type": "object", + "description": "The request made to the one-time-token generate endpoint.", + "required": [], + "additionalProperties": false, + + "properties": { + "expiresIn": { + "description": "Specify how long the token is valid for, in seconds.", + "type": "number" + } + } +} diff --git a/src/one-time-token/controller/generate.ts b/src/one-time-token/controller/generate.ts index 03a366c2..7d72fc48 100644 --- a/src/one-time-token/controller/generate.ts +++ b/src/one-time-token/controller/generate.ts @@ -5,16 +5,24 @@ import { createToken } from '../service'; import * as hal from '../formats/hal'; import { resolve } from 'url'; +type GenerateRequest = { + expiresIn?: number; +} + class OneTimeTokenController extends Controller { async post(ctx: Context) { + ctx.request.validate('https://curveballjs.org/schemas/a12nserver/one-time-token-generate.json'); ctx.privileges.require('a12n:one-time-token:generate'); const principalService = new PrincipalService(ctx.privileges); const user = await principalService.findByExternalId(ctx.params.id, 'user'); - const token = await createToken(user); + const token = await createToken( + user, + ctx.request.body.expiresIn ?? null, + ); const url = resolve(ctx.request.origin, 'reset-password/token/' + token.token); ctx.response.body = hal.oneTimeToken(user, url, token); diff --git a/src/one-time-token/service.ts b/src/one-time-token/service.ts index e0ed7197..c8e8847f 100644 --- a/src/one-time-token/service.ts +++ b/src/one-time-token/service.ts @@ -13,16 +13,16 @@ const tokenTTL = 7200; /** * This function will create a unique token then store it in the database */ -export async function createToken(user: User): Promise { +export async function createToken(user: User, expiresIn: number | null): Promise { const token = await generateSecretToken(); - const query = 'INSERT INTO reset_password_token (user_id, token, expires_at, created_at) VALUES (?, ?, ?, ?)'; + const expiresAt = Math.floor(Date.now() / 1000) + (expiresIn ?? tokenTTL); - await db.raw(query, [ - user.id, + await db('reset_password_token').insert({ + user_id: user.id, token, - Math.floor(Date.now() / 1000) + tokenTTL, - Math.floor(Date.now() / 1000), - ]); + expires_at: expiresAt, + created_at: Math.floor(Date.now() / 1000) + }); return { token, expires: new Date(Date.now() + tokenTTL*1000), diff --git a/src/reset-password/service.ts b/src/reset-password/service.ts index 6bf8757f..b811fbe7 100644 --- a/src/reset-password/service.ts +++ b/src/reset-password/service.ts @@ -17,7 +17,7 @@ export async function sendResetPasswordEmail(user: User) { const smtpUrl = requireSetting('smtp.url')!; const transporter = nodemailer.createTransport(smtpUrl); - const token = await createToken(user); + const token = await createToken(user, null); const emailTemplate = render('emails/reset-password-email', { name: user.nickname, url: process.env.PUBLIC_URI + 'reset-password/token/' + token.token,