Add identity tokens

This commit is contained in:
Jonas Lochmann 2022-09-12 02:00:00 +02:00
parent a86a0abb05
commit 04aa2ce517
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
18 changed files with 390 additions and 11 deletions

View file

@ -169,3 +169,28 @@ If there is no device with the specified ``deviceId``: HTTP status code 409 Conf
If the ``secondPasswordHash`` is invalid: HTTP status code 401 Unauthorized If the ``secondPasswordHash`` is invalid: HTTP status code 401 Unauthorized
On success: ``{"ok": true}`` On success: ``{"ok": true}``
## POST /parent/create-identity-token
Use this to get a identity token.
This can be used to inform the server operator about ones user account.
### request
see [this JSON schema](../schema/requestidentitytokenrequest.md)
in case of a device used by a parent with disabled password checks, use ``device`` as ``secondPasswordHash``
## response
On a invalid request body: HTTP status code 400 Bad Request
If the device auth token is invalid: HTTP status code 401 Unauthorized
If there is no device with the specified ``deviceId``: HTTP status code 409 Conflict
If the ``secondPasswordHash`` is invalid: HTTP status code 401 Unauthorized
If the server does not support this request: HTTP status code 404
On success: ``{"token": "some string"}``; you should not make any assumptions about the token string

View file

@ -26,6 +26,8 @@
* [RemoveDeviceRequest](./removedevicerequest.md) `https://timelimit.io/RemoveDeviceRequest` * [RemoveDeviceRequest](./removedevicerequest.md) `https://timelimit.io/RemoveDeviceRequest`
* [RequestIdentityTokenRequest](./requestidentitytokenrequest.md) `https://timelimit.io/RequestIdentityTokenRequest`
* [RequestWithAuthToken](./requestwithauthtoken.md) `https://timelimit.io/RequestWithAuthToken` * [RequestWithAuthToken](./requestwithauthtoken.md) `https://timelimit.io/RequestWithAuthToken`
* [SendMailLoginCodeRequest](./sendmaillogincoderequest.md) `https://timelimit.io/SendMailLoginCodeRequest` * [SendMailLoginCodeRequest](./sendmaillogincoderequest.md) `https://timelimit.io/SendMailLoginCodeRequest`

View file

@ -0,0 +1,31 @@
{
"type": "object",
"properties": {
"deviceAuthToken": {
"type": "string"
},
"parentUserId": {
"type": "string"
},
"parentPasswordSecondHash": {
"type": "string"
},
"purpose": {
"type": "string",
"enum": [
"purchase"
]
}
},
"additionalProperties": false,
"required": [
"deviceAuthToken",
"parentPasswordSecondHash",
"parentUserId",
"purpose"
],
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "RequestIdentityTokenRequest",
"$id": "https://timelimit.io/RequestIdentityTokenRequest"
}

View file

@ -0,0 +1,15 @@
# Untitled undefined type in RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest#/definitions
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [RequestIdentityTokenRequest.schema.json\*](RequestIdentityTokenRequest.schema.json "open original schema") |
## definitions Type
unknown

View file

@ -0,0 +1,15 @@
# Untitled string in RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest#/properties/deviceAuthToken
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [RequestIdentityTokenRequest.schema.json\*](RequestIdentityTokenRequest.schema.json "open original schema") |
## deviceAuthToken Type
`string`

View file

@ -0,0 +1,15 @@
# Untitled string in RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest#/properties/parentPasswordSecondHash
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [RequestIdentityTokenRequest.schema.json\*](RequestIdentityTokenRequest.schema.json "open original schema") |
## parentPasswordSecondHash Type
`string`

View file

@ -0,0 +1,15 @@
# Untitled string in RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest#/properties/parentUserId
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------- |
| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [RequestIdentityTokenRequest.schema.json\*](RequestIdentityTokenRequest.schema.json "open original schema") |
## parentUserId Type
`string`

View file

@ -0,0 +1,23 @@
# Untitled string in RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest#/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 | [RequestIdentityTokenRequest.schema.json\*](RequestIdentityTokenRequest.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"` | |

View file

@ -0,0 +1,106 @@
# RequestIdentityTokenRequest Schema
```txt
https://timelimit.io/RequestIdentityTokenRequest
```
| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------------------- |
| Can be instantiated | Yes | Unknown status | No | Forbidden | Forbidden | none | [RequestIdentityTokenRequest.schema.json](RequestIdentityTokenRequest.schema.json "open original schema") |
## RequestIdentityTokenRequest Type
`object` ([RequestIdentityTokenRequest](requestidentitytokenrequest.md))
# RequestIdentityTokenRequest Properties
| Property | Type | Required | Nullable | Defined by |
| :---------------------------------------------------- | :------- | :------- | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [deviceAuthToken](#deviceauthtoken) | `string` | Required | cannot be null | [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-deviceauthtoken.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/deviceAuthToken") |
| [parentUserId](#parentuserid) | `string` | Required | cannot be null | [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-parentuserid.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/parentUserId") |
| [parentPasswordSecondHash](#parentpasswordsecondhash) | `string` | Required | cannot be null | [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-parentpasswordsecondhash.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/parentPasswordSecondHash") |
| [purpose](#purpose) | `string` | Required | cannot be null | [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-purpose.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/purpose") |
## deviceAuthToken
`deviceAuthToken`
* is required
* Type: `string`
* cannot be null
* defined in: [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-deviceauthtoken.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/deviceAuthToken")
### deviceAuthToken Type
`string`
## parentUserId
`parentUserId`
* is required
* Type: `string`
* cannot be null
* defined in: [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-parentuserid.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/parentUserId")
### parentUserId Type
`string`
## parentPasswordSecondHash
`parentPasswordSecondHash`
* is required
* Type: `string`
* cannot be null
* defined in: [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-parentpasswordsecondhash.md "https://timelimit.io/RequestIdentityTokenRequest#/properties/parentPasswordSecondHash")
### parentPasswordSecondHash Type
`string`
## purpose
`purpose`
* is required
* Type: `string`
* cannot be null
* defined in: [RequestIdentityTokenRequest](requestidentitytokenrequest-properties-purpose.md "https://timelimit.io/RequestIdentityTokenRequest#/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"` | |
# RequestIdentityTokenRequest Definitions

View file

@ -54,3 +54,6 @@
- PING_INTERVAL_SEC - PING_INTERVAL_SEC
- ping interval at the websocket in seconds - ping interval at the websocket in seconds
- the default value is ``25`` - the default value is ``25``
- SIGN_SECRET
- used for signing tokens
- if not set or set to an empty string, then the features that depend on it are disabled

14
package-lock.json generated
View file

@ -16,6 +16,7 @@
"email-addresses": "^3.1.0", "email-addresses": "^3.1.0",
"express": "^4.17.1", "express": "^4.17.1",
"http-errors": "^1.8.0", "http-errors": "^1.8.0",
"jose": "^4.9.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mariadb": "^2.5.2", "mariadb": "^2.5.2",
"nodemailer": "^6.7.2", "nodemailer": "^6.7.2",
@ -2333,6 +2334,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/jose": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.9.3.tgz",
"integrity": "sha512-f8E/z+T3Q0kA9txzH2DKvH/ds2uggcw0m3vVPSB9HrSkrQ7mojjifvS7aR8cw+lQl2Fcmx9npwaHpM/M3GD8UQ==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@ -7071,6 +7080,11 @@
"minimatch": "^3.0.4" "minimatch": "^3.0.4"
} }
}, },
"jose": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.9.3.tgz",
"integrity": "sha512-f8E/z+T3Q0kA9txzH2DKvH/ds2uggcw0m3vVPSB9HrSkrQ7mojjifvS7aR8cw+lQl2Fcmx9npwaHpM/M3GD8UQ=="
},
"js-yaml": { "js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",

View file

@ -54,6 +54,7 @@
"email-addresses": "^3.1.0", "email-addresses": "^3.1.0",
"express": "^4.17.1", "express": "^4.17.1",
"http-errors": "^1.8.0", "http-errors": "^1.8.0",
"jose": "^4.9.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mariadb": "^2.5.2", "mariadb": "^2.5.2",
"nodemailer": "^6.7.2", "nodemailer": "^6.7.2",

View file

@ -1,6 +1,6 @@
/* /*
* server component for the TimeLimit App * server component for the TimeLimit App
* Copyright (C) 2019 - 2021 Jonas Lochmann * Copyright (C) 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -39,6 +39,7 @@ const types = [
'LinkParentMailAddressRequest', 'LinkParentMailAddressRequest',
'UpdatePrimaryDeviceRequest', 'UpdatePrimaryDeviceRequest',
'RemoveDeviceRequest', 'RemoveDeviceRequest',
'RequestIdentityTokenRequest',
'RequestWithAuthToken', 'RequestWithAuthToken',
'SendMailLoginCodeRequest', 'SendMailLoginCodeRequest',
'SignInByMailCodeRequest' 'SignInByMailCodeRequest'

View file

@ -1,6 +1,6 @@
/* /*
* server component for the TimeLimit App * server component for the TimeLimit App
* Copyright (C) 2019 - 2021 Jonas Lochmann * Copyright (C) 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -27,12 +27,13 @@ import { getStatusByMailToken } from '../function/parent/get-status-by-mail-addr
import { linkMailAddress } from '../function/parent/link-mail-address' import { linkMailAddress } from '../function/parent/link-mail-address'
import { recoverParentPassword } from '../function/parent/recover-parent-password' import { recoverParentPassword } from '../function/parent/recover-parent-password'
import { signInIntoFamily } from '../function/parent/sign-in-into-family' import { signInIntoFamily } from '../function/parent/sign-in-into-family'
import { createIdentityToken, MissingSignSecretException } from '../util/identity-token'
import { WebsocketApi } from '../websocket' import { WebsocketApi } from '../websocket'
import { import {
isCreateFamilyByMailTokenRequest, isCreateFamilyByMailTokenRequest,
isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest, isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest,
isMailAuthTokenRequestBody, isRecoverParentPasswordRequest, isMailAuthTokenRequestBody, isRecoverParentPasswordRequest,
isRemoveDeviceRequest, isSignIntoFamilyRequest isRemoveDeviceRequest, isSignIntoFamilyRequest, isRequestIdentityTokenRequest
} from './validator' } from './validator'
export const createParentRouter = ({ database, websocket }: {database: Database, websocket: WebsocketApi}) => { export const createParentRouter = ({ database, websocket }: {database: Database, websocket: WebsocketApi}) => {
@ -131,7 +132,7 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
}) })
async function assertAuthValidAndReturnDeviceEntry ({ deviceAuthToken, parentId, secondPasswordHash, transaction }: { async function assertAuthValidAndReturnDetails ({ deviceAuthToken, parentId, secondPasswordHash, transaction }: {
deviceAuthToken: string deviceAuthToken: string
parentId: string parentId: string
secondPasswordHash: string secondPasswordHash: string
@ -165,6 +166,8 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
if (!parentEntry) { if (!parentEntry) {
throw new Unauthorized() throw new Unauthorized()
} }
return { deviceEntry, parentEntry }
} else { } else {
const parentEntry = await database.user.findOne({ const parentEntry = await database.user.findOne({
where: { where: {
@ -179,9 +182,9 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
if (!parentEntry) { if (!parentEntry) {
throw new Unauthorized() throw new Unauthorized()
} }
}
return deviceEntry return { deviceEntry, parentEntry }
}
} }
router.post('/create-add-device-token', json(), async (req, res, next) => { router.post('/create-add-device-token', json(), async (req, res, next) => {
@ -191,7 +194,7 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
const { token, deviceId } = await database.transaction(async (transaction) => { const { token, deviceId } = await database.transaction(async (transaction) => {
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({ const { deviceEntry } = await assertAuthValidAndReturnDetails({
deviceAuthToken: req.body.deviceAuthToken, deviceAuthToken: req.body.deviceAuthToken,
parentId: req.body.parentId, parentId: req.body.parentId,
secondPasswordHash: req.body.parentPasswordSecondHash, secondPasswordHash: req.body.parentPasswordSecondHash,
@ -235,7 +238,7 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
await database.transaction(async (transaction) => { await database.transaction(async (transaction) => {
const deviceEntry = await assertAuthValidAndReturnDeviceEntry({ const { deviceEntry } = await assertAuthValidAndReturnDetails({
deviceAuthToken: req.body.deviceAuthToken, deviceAuthToken: req.body.deviceAuthToken,
parentId: req.body.parentUserId, parentId: req.body.parentUserId,
secondPasswordHash: req.body.parentPasswordSecondHash, secondPasswordHash: req.body.parentPasswordSecondHash,
@ -257,5 +260,36 @@ export const createParentRouter = ({ database, websocket }: {database: Database,
} }
}) })
router.post('/create-identity-token', json(), async (req, res, next) => {
try {
if (!isRequestIdentityTokenRequest(req.body)) {
throw new BadRequest()
}
const body = req.body
await database.transaction(async (transaction) => {
const { deviceEntry, parentEntry } = await assertAuthValidAndReturnDetails({
deviceAuthToken: body.deviceAuthToken,
parentId: body.parentUserId,
secondPasswordHash: body.parentPasswordSecondHash,
transaction
})
const token = await createIdentityToken({
purpose: body.purpose,
familyId: deviceEntry.familyId,
userId: parentEntry.userId,
mail: parentEntry.mail
})
res.json({ token })
})
} catch (ex) {
if (ex instanceof MissingSignSecretException) res.sendStatus(404)
else next(ex)
}
})
return router return router
} }

View file

@ -140,6 +140,13 @@ export interface RemoveDeviceRequest {
deviceId: string deviceId: string
} }
export interface RequestIdentityTokenRequest {
deviceAuthToken: string
parentUserId: string
parentPasswordSecondHash: string
purpose: 'purchase'
}
export interface RequestWithAuthToken { export interface RequestWithAuthToken {
deviceAuthToken: string deviceAuthToken: string
} }

View file

@ -1,5 +1,5 @@
// tslint:disable // tslint:disable
import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, 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 } from './schema'
import Ajv from 'ajv' import Ajv from 'ajv'
const ajv = new Ajv() const ajv = new Ajv()
@ -3253,6 +3253,35 @@ export const isRemoveDeviceRequest: (value: unknown) => value is RemoveDeviceReq
"definitions": definitions, "definitions": definitions,
"$schema": "http://json-schema.org/draft-07/schema#" "$schema": "http://json-schema.org/draft-07/schema#"
}) })
export const isRequestIdentityTokenRequest: (value: unknown) => value is RequestIdentityTokenRequest = ajv.compile({
"type": "object",
"properties": {
"deviceAuthToken": {
"type": "string"
},
"parentUserId": {
"type": "string"
},
"parentPasswordSecondHash": {
"type": "string"
},
"purpose": {
"type": "string",
"enum": [
"purchase"
]
}
},
"additionalProperties": false,
"required": [
"deviceAuthToken",
"parentPasswordSecondHash",
"parentUserId",
"purpose"
],
"definitions": definitions,
"$schema": "http://json-schema.org/draft-07/schema#"
})
export const isRequestWithAuthToken: (value: unknown) => value is RequestWithAuthToken = ajv.compile({ export const isRequestWithAuthToken: (value: unknown) => value is RequestWithAuthToken = ajv.compile({
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -1,6 +1,6 @@
/* /*
* server component for the TimeLimit App * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -21,6 +21,7 @@ interface Config {
disableSignup: boolean disableSignup: boolean
pingInterval: number pingInterval: number
alwaysPro: boolean alwaysPro: boolean
signSecret: string
} }
function parseYesNo (value: string) { function parseYesNo (value: string) {
@ -37,7 +38,8 @@ export const config: Config = {
mailWhitelist: (process.env.MAIL_WHITELIST || '').split(',').map((item) => item.trim()).filter((item) => item.length > 0), mailWhitelist: (process.env.MAIL_WHITELIST || '').split(',').map((item) => item.trim()).filter((item) => item.length > 0),
disableSignup: parseYesNo(process.env.DISABLE_SIGNUP || 'no'), disableSignup: parseYesNo(process.env.DISABLE_SIGNUP || 'no'),
pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000, pingInterval: parseInt(process.env.PING_INTERVAL_SEC || '25', 10) * 1000,
alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false alwaysPro: process.env.ALWAYS_PRO ? parseYesNo(process.env.ALWAYS_PRO) : false,
signSecret: process.env.SIGN_SECRET || ''
} }
class ParseYesNoException extends Error {} class ParseYesNoException extends Error {}

View file

@ -0,0 +1,41 @@
/*
* server component for the TimeLimit App
* 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
* published by the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { SignJWT } from 'jose'
import { config } from '../config'
export async function createIdentityToken({ purpose, familyId, userId, mail }: {
purpose: string
familyId: string
userId: string
mail: string
}) {
if (config.signSecret === '') throw new MissingSignSecretException()
const jwt = await new SignJWT({ purpose, familyId, userId, mail })
.setExpirationTime('7d')
.setProtectedHeader({ alg: 'HS512' })
.sign(Buffer.from(config.signSecret, 'utf8'))
return Buffer.from(jwt, 'ascii')
.toString('base64')
.split(/(.{32})/)
.filter((item) => item.length > 0)
.join('\n')
}
export class MissingSignSecretException extends Error {}