mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
5.0 KiB
193 lines
5.0 KiB
import type { FastifyInstance } from 'fastify'
|
|
import type { KeyExpiration } from '../models/apiKeys.ts'
|
|
|
|
/**
|
|
* API Keys Routes
|
|
*/
|
|
async function routes(app: FastifyInstance) {
|
|
/**
|
|
* LIST API KEYS
|
|
*/
|
|
app.get(
|
|
'/',
|
|
{
|
|
config: {
|
|
permissions: ['manage:system']
|
|
},
|
|
schema: {
|
|
summary: 'List all API keys',
|
|
description:
|
|
'Revoked and expired keys are listed too, so that the admin area can show their state.',
|
|
tags: ['API Keys'],
|
|
response: {
|
|
200: {
|
|
description: 'List of API keys',
|
|
type: 'array',
|
|
items: { $ref: 'ApiKey#' }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
async () => {
|
|
return WIKI.models.apiKeys.getKeys()
|
|
}
|
|
)
|
|
|
|
/**
|
|
* CREATE API KEY
|
|
*/
|
|
app.post<{
|
|
Body: { name: string; expiration: KeyExpiration; groups: string[] }
|
|
}>(
|
|
'/',
|
|
{
|
|
config: {
|
|
permissions: ['manage:system']
|
|
},
|
|
schema: {
|
|
summary: 'Create a new API key',
|
|
description:
|
|
'The response carries the token, which is the only time it can be read: only its last characters are stored. The key holds the combined permissions of the groups given.',
|
|
tags: ['API Keys'],
|
|
body: {
|
|
type: 'object',
|
|
required: ['name', 'expiration', 'groups'],
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
minLength: 1,
|
|
maxLength: 255,
|
|
description: 'What the key is for.'
|
|
},
|
|
expiration: { $ref: 'ApiKeyExpiration#' },
|
|
groups: {
|
|
type: 'array',
|
|
minItems: 1,
|
|
description:
|
|
'Groups whose permissions the key carries. The guests group is not accepted.',
|
|
items: {
|
|
type: 'string',
|
|
format: 'uuid'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
response: {
|
|
200: {
|
|
description: 'API key created successfully',
|
|
type: 'object',
|
|
properties: {
|
|
ok: {
|
|
type: 'boolean'
|
|
},
|
|
message: {
|
|
type: 'string'
|
|
},
|
|
id: {
|
|
type: 'string',
|
|
format: 'uuid'
|
|
},
|
|
key: {
|
|
type: 'string',
|
|
description: 'The token. Shown once and never again.'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
async (req, reply) => {
|
|
if (!/^[^<>"]+$/.test(req.body.name)) {
|
|
return reply.badRequest('Key name contains invalid characters.')
|
|
}
|
|
|
|
// -> A key inherits group permissions, so every group must exist; a stale client should not
|
|
// silently mint a key with fewer permissions than the operator picked
|
|
const known = await WIKI.models.groups.getAllGroups()
|
|
for (const groupId of req.body.groups) {
|
|
if (!known.some((g) => g.id === groupId)) {
|
|
return reply.badRequest('One of the groups does not exist.')
|
|
}
|
|
// -> Guests are anonymous visitors: a key holding their permissions grants nothing a caller
|
|
// could not already do without one
|
|
if (groupId === WIKI.data.systemIds.guestsGroupId) {
|
|
return reply.badRequest('The guests group cannot be used for API keys.')
|
|
}
|
|
}
|
|
|
|
const { id, key } = await WIKI.models.apiKeys.createKey({
|
|
name: req.body.name,
|
|
expiration: req.body.expiration,
|
|
groups: req.body.groups
|
|
})
|
|
|
|
return {
|
|
ok: true,
|
|
message: 'API key created successfully.',
|
|
id,
|
|
key
|
|
}
|
|
}
|
|
)
|
|
|
|
/**
|
|
* REVOKE API KEY
|
|
*/
|
|
app.post<{ Params: { keyId: string } }>(
|
|
'/:keyId/revoke',
|
|
{
|
|
config: {
|
|
permissions: ['manage:system']
|
|
},
|
|
schema: {
|
|
summary: 'Revoke an API key',
|
|
description:
|
|
'Permanent: the key stays listed as revoked and stops authenticating on the next request. Keys are never deleted, so the record of what existed is kept.',
|
|
tags: ['API Keys'],
|
|
params: {
|
|
type: 'object',
|
|
properties: {
|
|
keyId: {
|
|
type: 'string',
|
|
format: 'uuid'
|
|
}
|
|
},
|
|
required: ['keyId']
|
|
},
|
|
response: {
|
|
200: {
|
|
description: 'API key revoked successfully',
|
|
type: 'object',
|
|
properties: {
|
|
ok: {
|
|
type: 'boolean'
|
|
},
|
|
message: {
|
|
type: 'string'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
async (req, reply) => {
|
|
const key = await WIKI.models.apiKeys.getKeyById(req.params.keyId)
|
|
if (!key) {
|
|
return reply.notFound('API key does not exist.')
|
|
}
|
|
if (key.isRevoked) {
|
|
return reply.conflict('This API key is already revoked.')
|
|
}
|
|
|
|
await WIKI.models.apiKeys.revokeKey(key.id)
|
|
|
|
return {
|
|
ok: true,
|
|
message: 'API key revoked successfully.'
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
export default routes
|