Make it work.
Some checks failed
CI / build (20.x) (push) Has been cancelled
CI / build (22.x) (push) Has been cancelled

This commit is contained in:
2025-07-20 04:26:50 -05:00
parent 5ab210f2bf
commit 73109c42a0
6 changed files with 500 additions and 146 deletions

View File

@ -1,5 +1,5 @@
// useraccesstokens plugin, server-side component
// These handlers are launched with the wiki server.
// useraccesstokens plugin, TokenManager class
// Provides token management functionality for the security enhancer
import fs from 'node:fs'
import fsp from 'node:fs/promises'
@ -57,7 +57,7 @@ class TokenManager {
const tokens = await this.loadTokens()
// Check if token name already exists for this user
const existingToken = tokens.find(t => t.user === user && t.name === name && !t.revoked)
const existingToken = tokens.find(t => JSON.stringify(t.user) === JSON.stringify(user) && t.name === name && !t.revoked)
if (existingToken) {
throw new Error(`Token with name "${name}" already exists`)
}
@ -87,17 +87,20 @@ class TokenManager {
async listTokens(user) {
const tokens = await this.loadTokens()
return tokens
.filter(t => t.user === user)
.map(t => {
const { tokenHash, ...safeToken } = t
return safeToken
})
const filteredTokens = tokens.filter(t => {
return JSON.stringify(t.user) === JSON.stringify(user)
})
return filteredTokens.map(t => {
const { tokenHash, ...safeToken } = t
return safeToken
})
}
async revokeToken(user, name) {
const tokens = await this.loadTokens()
const tokenIndex = tokens.findIndex(t => t.user === user && t.name === name)
const tokenIndex = tokens.findIndex(t => JSON.stringify(t.user) === JSON.stringify(user) && t.name === name)
if (tokenIndex === -1) {
throw new Error(`Token "${name}" not found`)
@ -111,7 +114,7 @@ class TokenManager {
async deleteToken(user, name) {
const tokens = await this.loadTokens()
const tokenIndex = tokens.findIndex(t => t.user === user && t.name === name)
const tokenIndex = tokens.findIndex(t => JSON.stringify(t.user) === JSON.stringify(user) && t.name === name)
if (tokenIndex === -1) {
throw new Error(`Token "${name}" not found`)
@ -148,124 +151,4 @@ class TokenManager {
}
}
const startServer = async function (params) {
const { app, argv } = params
// Initialize token manager
const tokenManager = new TokenManager(argv.status)
// Middleware to check if user is authenticated
const authenticated = function (req, res, next) {
if (!app.securityhandler.isAuthorized(req)) {
return res.status(401).json({ error: 'Must be authenticated' })
}
return next()
}
// Middleware to get current user
const getCurrentUser = function (req) {
return app.securityhandler.getUser(req)
}
// API Routes
// POST /plugin/useraccesstokens/create - Create a new token
app.post('/plugin/useraccesstokens/create', authenticated, async (req, res) => {
try {
const user = getCurrentUser(req)
const { name, expiresInDays } = req.body
if (!name || typeof name !== 'string' || name.trim() === '') {
return res.status(400).json({ error: 'Token name is required' })
}
const result = await tokenManager.createToken(user, name.trim(), expiresInDays)
res.json({
token: result.token,
name: result.record.name,
displayHint: result.record.displayHint,
created: result.record.created,
expires: result.record.expires,
scopes: result.record.scopes
})
} catch (error) {
res.status(400).json({ error: error.message })
}
})
// GET /plugin/useraccesstokens/list - List user's tokens
app.get('/plugin/useraccesstokens/list', authenticated, async (req, res) => {
try {
const user = getCurrentUser(req)
const tokens = await tokenManager.listTokens(user)
res.json(tokens)
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// POST /plugin/useraccesstokens/revoke - Revoke a token
app.post('/plugin/useraccesstokens/revoke', authenticated, async (req, res) => {
try {
const user = getCurrentUser(req)
const { name } = req.body
if (!name) {
return res.status(400).json({ error: 'Token name is required' })
}
await tokenManager.revokeToken(user, name)
res.json({ message: `Token "${name}" revoked successfully` })
} catch (error) {
res.status(400).json({ error: error.message })
}
})
// DELETE /plugin/useraccesstokens/delete - Delete a token
app.delete('/plugin/useraccesstokens/delete', authenticated, async (req, res) => {
try {
const user = getCurrentUser(req)
const { name } = req.body
if (!name) {
return res.status(400).json({ error: 'Token name is required' })
}
await tokenManager.deleteToken(user, name)
res.json({ message: `Token "${name}" deleted successfully` })
} catch (error) {
res.status(400).json({ error: error.message })
}
})
// Middleware for token-based authentication
app.use('/api', async (req, res, next) => {
const authHeader = req.headers.authorization
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7)
try {
const tokenRecord = await tokenManager.validateToken(token)
if (tokenRecord) {
// Set user context for token-based requests
req.tokenAuth = {
user: tokenRecord.user,
scopes: tokenRecord.scopes,
tokenName: tokenRecord.name
}
return next()
}
} catch (error) {
console.error('Token validation error:', error)
}
}
next()
})
console.log('UserAccessTokens plugin server started')
}
export { startServer, TokenManager }
export { TokenManager }