Files
huly-platform/server/token/src/token.ts
T

119 lines
2.8 KiB
TypeScript

import { AccountRole, AccountUuid, MeasureContext, PersonUuid, Ref, Space, WorkspaceUuid } from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import { decode, encode } from 'jwt-simple'
import { validate } from 'uuid'
import serverPlugin from './plugin'
/**
* @public
*/
export interface Token {
account: AccountUuid
workspace: WorkspaceUuid
extra?: Record<string, any>
grant?: PermissionsGrant
}
// Permissions grant provides the token presenter access to a specific workspace
export interface PermissionsGrant {
workspace: WorkspaceUuid
role: AccountRole
firstName?: string
lastName?: string
spaces?: Ref<Space>[]
extra?: Record<string, any>
}
/**
* @public
*/
export class TokenError extends Error {
constructor (message: string) {
super(message)
this.name = 'TokenError'
}
}
const getSecret = (): string => {
return getMetadata(serverPlugin.metadata.Secret) ?? 'secret'
}
/**
* @public
*/
export function generateToken (
accountUuid: PersonUuid,
workspaceUuid?: WorkspaceUuid,
extra?: Record<string, string>,
secret?: string,
grant?: PermissionsGrant
): string {
if (!validate(accountUuid)) {
throw new TokenError(`Invalid account uuid: "${accountUuid}"`)
}
if (workspaceUuid !== undefined && !validate(workspaceUuid)) {
throw new TokenError(`Invalid workspace uuid: "${workspaceUuid}"`)
}
if (grant?.workspace !== undefined && !validate(grant?.workspace)) {
throw new TokenError(`Invalid grant workspace uuid: "${grant?.workspace}"`)
}
const service = getMetadata(serverPlugin.metadata.Service)
if (service !== undefined) {
extra = { service, ...extra }
}
const sanitizedGrant: PermissionsGrant | undefined =
grant !== undefined
? {
workspace: grant.workspace,
role: grant.role,
firstName: grant.firstName,
lastName: grant.lastName,
spaces: grant.spaces,
extra: grant.extra
}
: undefined
return encode(
{
...(extra !== undefined ? { extra } : {}),
account: accountUuid,
workspace: workspaceUuid,
grant: sanitizedGrant
},
secret ?? getSecret()
)
}
/**
* @public
*/
export function decodeToken (token: string, verify: boolean = true, secret?: string): Token {
try {
return decode(token, secret ?? getSecret(), !verify)
} catch (err: any) {
throw new TokenError(err.message)
}
}
/**
* @public
*/
export function decodeTokenVerbose (ctx: MeasureContext, token: string): Token {
try {
return decodeToken(token)
} catch (err: any) {
try {
const decode = decodeToken(token, false)
ctx.warn('Failed to verify token', { ...decode })
} catch (err2: any) {
// Nothing to do
}
throw new TokenError(err.message)
}
}