diff --git a/docs/api/admin.md b/docs/api/admin.md index 62d9c12..2eb2ed2 100644 --- a/docs/api/admin.md +++ b/docs/api/admin.md @@ -89,3 +89,41 @@ If there was nothing found for the mail address: HTTP status code 409 Conflict ### see - [premium concept](../concept/premium.md) + +## POST /admin/unlock-premium-v2 + +Use this to unlock all features for one user for a specified duration. + +### request + +request properties: ``purchaseToken`` and ``purchaseId`` + +- ``purchasetoken`` is a string which the client shows at the purchase screen +- ``purchaseId`` is the ID that is used at the bill + +### response + +The response contains the following properties: + +- ``ok`` (boolean) +- ``error`` + - string + - set if and only if ``ok`` is false + - possible values + - ``token invalid`` + - ``illegal state`` + - ``purchase id already used`` +- ``detail`` + - optional string + - should be shown to the support +- ``lastPurchase`` + - optional object + - should be shown to the support +- ``wasAlreadyExecuted`` (boolean, set if and only if ``ok`` is true) + +If the request was malformed: HTTP status code 400 Bad Request + +Using the same ``purchaseId`` twice results in: + +- ``wasAlreadyExecuted`` if the familyId is unchanged +- ``error`` = ``purchase id already used`` otherwise diff --git a/docs/schema/IdentityTokenPayload.schema.json b/docs/schema/IdentityTokenPayload.schema.json new file mode 100644 index 0000000..b27b591 --- /dev/null +++ b/docs/schema/IdentityTokenPayload.schema.json @@ -0,0 +1,35 @@ +{ + "additionalProperties": false, + "type": "object", + "properties": { + "purpose": { + "type": "string", + "enum": [ + "purchase" + ] + }, + "familyId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "exp": { + "type": "number" + } + }, + "required": [ + "exp", + "familyId", + "mail", + "purpose", + "userId" + ], + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityTokenPayload", + "$id": "https://timelimit.io/IdentityTokenPayload" +} \ No newline at end of file diff --git a/docs/schema/README.md b/docs/schema/README.md index 48d230a..06a9092 100644 --- a/docs/schema/README.md +++ b/docs/schema/README.md @@ -16,6 +16,8 @@ * [FinishPurchaseByGooglePlayRequest](./finishpurchasebygoogleplayrequest.md) – `https://timelimit.io/FinishPurchaseByGooglePlayRequest` +* [IdentityTokenPayload](./identitytokenpayload.md) – `https://timelimit.io/IdentityTokenPayload` + * [LinkParentMailAddressRequest](./linkparentmailaddressrequest.md) – `https://timelimit.io/LinkParentMailAddressRequest` * [MailAuthTokenRequestBody](./mailauthtokenrequestbody.md) – `https://timelimit.io/MailAuthTokenRequestBody` diff --git a/docs/schema/identitytokenpayload-definitions.md b/docs/schema/identitytokenpayload-definitions.md new file mode 100644 index 0000000..1cc84ed --- /dev/null +++ b/docs/schema/identitytokenpayload-definitions.md @@ -0,0 +1,15 @@ +# Untitled undefined type in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/definitions +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## definitions Type + +unknown diff --git a/docs/schema/identitytokenpayload-properties-exp.md b/docs/schema/identitytokenpayload-properties-exp.md new file mode 100644 index 0000000..86324c5 --- /dev/null +++ b/docs/schema/identitytokenpayload-properties-exp.md @@ -0,0 +1,15 @@ +# Untitled number in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/properties/exp +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## exp Type + +`number` diff --git a/docs/schema/identitytokenpayload-properties-familyid.md b/docs/schema/identitytokenpayload-properties-familyid.md new file mode 100644 index 0000000..6ac7f9e --- /dev/null +++ b/docs/schema/identitytokenpayload-properties-familyid.md @@ -0,0 +1,15 @@ +# Untitled string in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/properties/familyId +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## familyId Type + +`string` diff --git a/docs/schema/identitytokenpayload-properties-mail.md b/docs/schema/identitytokenpayload-properties-mail.md new file mode 100644 index 0000000..7ad72d2 --- /dev/null +++ b/docs/schema/identitytokenpayload-properties-mail.md @@ -0,0 +1,15 @@ +# Untitled string in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/properties/mail +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## mail Type + +`string` diff --git a/docs/schema/identitytokenpayload-properties-purpose.md b/docs/schema/identitytokenpayload-properties-purpose.md new file mode 100644 index 0000000..69d8d2a --- /dev/null +++ b/docs/schema/identitytokenpayload-properties-purpose.md @@ -0,0 +1,23 @@ +# Untitled string in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/properties/purpose +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## purpose Type + +`string` + +## purpose Constraints + +**enum**: the value of this property must be equal to one of the following values: + +| Value | Explanation | +| :----------- | :---------- | +| `"purchase"` | | diff --git a/docs/schema/identitytokenpayload-properties-userid.md b/docs/schema/identitytokenpayload-properties-userid.md new file mode 100644 index 0000000..7308504 --- /dev/null +++ b/docs/schema/identitytokenpayload-properties-userid.md @@ -0,0 +1,15 @@ +# Untitled string in IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload#/properties/userId +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [IdentityTokenPayload.schema.json\*](IdentityTokenPayload.schema.json "open original schema") | + +## userId Type + +`string` diff --git a/docs/schema/identitytokenpayload.md b/docs/schema/identitytokenpayload.md new file mode 100644 index 0000000..03ecd6c --- /dev/null +++ b/docs/schema/identitytokenpayload.md @@ -0,0 +1,125 @@ +# IdentityTokenPayload Schema + +```txt +https://timelimit.io/IdentityTokenPayload +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------ | +| Can be instantiated | Yes | Unknown status | No | Forbidden | Forbidden | none | [IdentityTokenPayload.schema.json](IdentityTokenPayload.schema.json "open original schema") | + +## IdentityTokenPayload Type + +`object` ([IdentityTokenPayload](identitytokenpayload.md)) + +# IdentityTokenPayload Properties + +| Property | Type | Required | Nullable | Defined by | +| :-------------------- | :------- | :------- | :------------- | :----------------------------------------------------------------------------------------------------------------------------------- | +| [purpose](#purpose) | `string` | Required | cannot be null | [IdentityTokenPayload](identitytokenpayload-properties-purpose.md "https://timelimit.io/IdentityTokenPayload#/properties/purpose") | +| [familyId](#familyid) | `string` | Required | cannot be null | [IdentityTokenPayload](identitytokenpayload-properties-familyid.md "https://timelimit.io/IdentityTokenPayload#/properties/familyId") | +| [userId](#userid) | `string` | Required | cannot be null | [IdentityTokenPayload](identitytokenpayload-properties-userid.md "https://timelimit.io/IdentityTokenPayload#/properties/userId") | +| [mail](#mail) | `string` | Required | cannot be null | [IdentityTokenPayload](identitytokenpayload-properties-mail.md "https://timelimit.io/IdentityTokenPayload#/properties/mail") | +| [exp](#exp) | `number` | Required | cannot be null | [IdentityTokenPayload](identitytokenpayload-properties-exp.md "https://timelimit.io/IdentityTokenPayload#/properties/exp") | + +## purpose + + + +`purpose` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [IdentityTokenPayload](identitytokenpayload-properties-purpose.md "https://timelimit.io/IdentityTokenPayload#/properties/purpose") + +### purpose Type + +`string` + +### purpose Constraints + +**enum**: the value of this property must be equal to one of the following values: + +| Value | Explanation | +| :----------- | :---------- | +| `"purchase"` | | + +## familyId + + + +`familyId` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [IdentityTokenPayload](identitytokenpayload-properties-familyid.md "https://timelimit.io/IdentityTokenPayload#/properties/familyId") + +### familyId Type + +`string` + +## userId + + + +`userId` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [IdentityTokenPayload](identitytokenpayload-properties-userid.md "https://timelimit.io/IdentityTokenPayload#/properties/userId") + +### userId Type + +`string` + +## mail + + + +`mail` + +* is required + +* Type: `string` + +* cannot be null + +* defined in: [IdentityTokenPayload](identitytokenpayload-properties-mail.md "https://timelimit.io/IdentityTokenPayload#/properties/mail") + +### mail Type + +`string` + +## exp + + + +`exp` + +* is required + +* Type: `number` + +* cannot be null + +* defined in: [IdentityTokenPayload](identitytokenpayload-properties-exp.md "https://timelimit.io/IdentityTokenPayload#/properties/exp") + +### exp Type + +`number` + +# IdentityTokenPayload Definitions diff --git a/scripts/build-schemas.js b/scripts/build-schemas.js index 295b3de..c62df9e 100644 --- a/scripts/build-schemas.js +++ b/scripts/build-schemas.js @@ -42,7 +42,8 @@ const types = [ 'RequestIdentityTokenRequest', 'RequestWithAuthToken', 'SendMailLoginCodeRequest', - 'SignInByMailCodeRequest' + 'SignInByMailCodeRequest', + 'IdentityTokenPayload' ] const docOnlyTypes = [ diff --git a/src/api/admin.ts b/src/api/admin.ts index 5a869ec..22ebf23 100644 --- a/src/api/admin.ts +++ b/src/api/admin.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -18,11 +18,13 @@ import { json } from 'body-parser' import { Router } from 'express' import { BadRequest, Conflict } from 'http-errors' +import * as Sequelize from 'sequelize' import { Database } from '../database' -import { addPurchase } from '../function/purchase' +import { addPurchase, canDoNextPurchase } from '../function/purchase' import { getStatusMessage, setStatusMessage } from '../function/statusmessage' import { EventHandler } from '../monitoring/eventhandler' import { generatePurchaseId } from '../util/token' +import { verifyIdentitifyToken, TokenValidationException } from '../util/identity-token' import { WebsocketApi } from '../websocket' export const createAdminRouter = ({ database, websocket, eventHandler }: { @@ -120,7 +122,8 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: { database, familyId: userEntry.familyId, type, - transactionId: 'manual-' + type + '-' + generatePurchaseId(), + service: 'directpurchase', + transactionId: 'legacyunlock-' + type + '-' + generatePurchaseId(), websocket, transaction }) @@ -132,5 +135,149 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: { } }) + router.post('/unlock-premium-v2', json(), async (req, res, next) => { + try { + if ( + typeof req.body !== 'object' || + typeof req.body.purchaseToken !== 'string' || + typeof req.body.purchaseId !== 'string' + ) { + throw new BadRequest() + } + + const purchaseToken: string = req.body.purchaseToken + const purchaseId: string = req.body.purchaseId + + const tokenContent = await verifyIdentitifyToken(purchaseToken) + + if (tokenContent.purpose !== 'purchase') { + res.json({ ok: false, error: 'token invalid', detail: 'wrong purpose' }) + + return + } + + const response = await database.transaction(async (transaction) => { + const userValid = await database.user.count({ + where: { + familyId: tokenContent.familyId, + userId: tokenContent.userId, + mail: tokenContent.mail, + type: 'parent' + }, + transaction + }) + + if (!userValid) return { + ok: false, + error: 'token invalid', + detail: 'user not found' + } + + let mailToReturn: string + + if (tokenContent.mail !== '') mailToReturn = tokenContent.mail + else { + const userEntryWithMail = await database.user.findOne({ + where: { + familyId: tokenContent.familyId, + mail: { + [Sequelize.Op.ne]: '' + }, + type: 'parent' + }, + transaction + }) + + if (!userEntryWithMail) return { + ok: false, + error: 'illegal state', + detail: 'no user with mail found' + } + + mailToReturn = userEntryWithMail.mail + } + + let wasAlreadyExecuted: boolean + + const oldPurchaseByPurchaseId = await database.purchase.findOne({ + where: { + service: 'directpurchase', + transactionId: purchaseId + } + }) + + if (oldPurchaseByPurchaseId === null) wasAlreadyExecuted = false + else if (oldPurchaseByPurchaseId.familyId === tokenContent.familyId) wasAlreadyExecuted = true + else return { + ok: false, + error: 'purchase id already used' + } + + if (!wasAlreadyExecuted) { + const familyEntry = await database.family.findOne({ + where: { + familyId: tokenContent.familyId + }, + transaction + }) + + if (!familyEntry) return { + ok: false, + error: 'family not found' + } + + const canDoPurchase = canDoNextPurchase({ fullVersionUntil: parseInt(familyEntry.fullVersionUntil) }) + + if (!canDoPurchase) { + const lastPurchase = await database.purchase.findOne({ + where: { + familyId: tokenContent.familyId + }, + transaction, + order: [['loggedAt', 'DESC']], + limit: 1 + }) + + return { + ok: false, + error: 'can not renew now', + lastPurchase: lastPurchase ? { + service: lastPurchase.service, + transactionId: lastPurchase.transactionId, + timestamp: parseInt(lastPurchase.loggedAt), + timestring: new Date(parseInt(lastPurchase.loggedAt)).toISOString() + } : undefined + } + } + + await addPurchase({ + database, + familyId: tokenContent.familyId, + type: 'year', + service: 'directpurchase', + transactionId: purchaseId, + websocket, + transaction + }) + } + + return { + ok: true, + mail: mailToReturn, + wasAlreadyExecuted + } + }) + + res.json(response) + } catch (ex) { + if (ex instanceof TokenValidationException) res.json({ + ok: false, + error: 'token invalid', + detail: ex.message + }) + else next(ex) + } + }) + return router } diff --git a/src/api/purchase.ts b/src/api/purchase.ts index d88e17c..da2ff4d 100644 --- a/src/api/purchase.ts +++ b/src/api/purchase.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -122,6 +122,7 @@ export const createPurchaseRouter = ({ database, websocket }: { database, familyId: deviceEntry.familyId, type, + service: 'googleplay', transactionId: orderId, websocket, transaction diff --git a/src/api/schema.ts b/src/api/schema.ts index eba769d..d3d703a 100644 --- a/src/api/schema.ts +++ b/src/api/schema.ts @@ -162,5 +162,16 @@ export interface SignInByMailCodeRequest { receivedCode: string } +export interface IdentityTokenCreatePayload { + purpose: 'purchase' + familyId: string + userId: string + mail: string +} + +export type IdentityTokenPayload = IdentityTokenCreatePayload & { + exp: number +} + export { SerializedParentAction, SerializedChildAction, SerializedAppLogicAction } from '../action/serialization' export { ServerDataStatus } from '../object/serverdatastatus' diff --git a/src/api/validator.ts b/src/api/validator.ts index d26d382..4d4f9ca 100644 --- a/src/api/validator.ts +++ b/src/api/validator.ts @@ -1,5 +1,5 @@ // tslint:disable -import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest } from './schema' +import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest, IdentityTokenPayload } from './schema' import Ajv from 'ajv' const ajv = new Ajv() @@ -3451,3 +3451,36 @@ export const isSignInByMailCodeRequest: (value: unknown) => value is SignInByMai "definitions": definitions, "$schema": "http://json-schema.org/draft-07/schema#" }) +export const isIdentityTokenPayload: (value: unknown) => value is IdentityTokenPayload = ajv.compile({ + "additionalProperties": false, + "type": "object", + "properties": { + "purpose": { + "type": "string", + "enum": [ + "purchase" + ] + }, + "familyId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "exp": { + "type": "number" + } + }, + "required": [ + "exp", + "familyId", + "mail", + "purpose", + "userId" + ], + "definitions": definitions, + "$schema": "http://json-schema.org/draft-07/schema#" +}) diff --git a/src/database/purchase.ts b/src/database/purchase.ts index ab0ed41..e85692d 100644 --- a/src/database/purchase.ts +++ b/src/database/purchase.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2022 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -21,7 +21,7 @@ import { SequelizeAttributes } from './types' export interface PurchaseAttributes { familyId: string - service: 'googleplay' + service: 'googleplay' | 'directpurchase' transactionId: string type: 'month' | 'year' loggedAt: string @@ -37,7 +37,7 @@ export type PurchaseModelStatic = typeof Sequelize.Model & { export const attributes: SequelizeAttributes = { familyId: { ...familyIdColumn }, service: { - ...createEnumColumn(['googleplay']), + ...createEnumColumn(['googleplay', 'directpurchase']), primaryKey: true }, transactionId: { diff --git a/src/function/purchase/add-purchase.ts b/src/function/purchase/add-purchase.ts index ed7e8c5..ed11fdf 100644 --- a/src/function/purchase/add-purchase.ts +++ b/src/function/purchase/add-purchase.ts @@ -24,16 +24,15 @@ const day = 1000 * 60 * 60 * 24 const month = day * 31 const year = day * 366 -export const addPurchase = async ({ database, familyId, type, transactionId, websocket, transaction }: { +export const addPurchase = async ({ database, familyId, type, service, transactionId, websocket, transaction }: { database: Database familyId: string type: 'month' | 'year' + service: 'googleplay' | 'directpurchase' transactionId: string websocket: WebsocketApi transaction: Transaction }) => { - const service = 'googleplay' - const oldPurchaseEntry = await database.purchase.findOne({ where: { service, diff --git a/src/util/identity-token.ts b/src/util/identity-token.ts index 1704ba3..fb147a3 100644 --- a/src/util/identity-token.ts +++ b/src/util/identity-token.ts @@ -15,21 +15,16 @@ * along with this program. If not, see . */ -import { SignJWT } from 'jose' +import { SignJWT, jwtVerify } from 'jose' import { config } from '../config' +import { IdentityTokenPayload, IdentityTokenCreatePayload } from '../api/schema' +import { isIdentityTokenPayload } from '../api/validator' -export async function createIdentityToken({ purpose, familyId, userId, mail }: { - purpose: string - familyId: string - userId: string - mail: string -}) { - if (config.signSecret === '') throw new MissingSignSecretException() - +export async function createIdentityToken({ purpose, familyId, userId, mail }: IdentityTokenCreatePayload) { const jwt = await new SignJWT({ purpose, familyId, userId, mail }) .setExpirationTime('7d') .setProtectedHeader({ alg: 'HS512' }) - .sign(Buffer.from(config.signSecret, 'utf8')) + .sign(getSignSecret()) return Buffer.from(jwt, 'ascii') .toString('base64') @@ -38,4 +33,31 @@ export async function createIdentityToken({ purpose, familyId, userId, mail }: { .join('\n') } +export async function verifyIdentitifyToken(token: string): Promise { + try { + const { payload } = await jwtVerify( + Buffer.from(token, 'base64').toString('ascii'), + getSignSecret(), + { algorithms: ['HS512'] } + ) + + if (!isIdentityTokenPayload(payload)) throw new BadPayloadException() + + return payload + } catch (ex) { + if (ex instanceof TokenValidationException) throw ex + else if (ex instanceof Error) throw new TokenValidationException(ex.message) + else throw ex + } +} + +function getSignSecret(): Buffer { + if (config.signSecret === '') throw new MissingSignSecretException() + + return Buffer.from(config.signSecret, 'utf8') +} + export class MissingSignSecretException extends Error {} + +export class TokenValidationException extends Error {} +class BadPayloadException extends TokenValidationException { constructor() { super('bad payload') } }