219 lines
6.7 KiB
JavaScript
219 lines
6.7 KiB
JavaScript
import { useraccesstokens } from '../src/client/useraccesstokens.js'
|
|
import { suite, test } from 'node:test'
|
|
import assert from 'node:assert'
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { TokenManager } from '../server/server.js'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
|
|
suite('useraccesstokens plugin', () => {
|
|
suite('client expand', () => {
|
|
test('can escape html markup characters', () => {
|
|
const result = useraccesstokens.expand('try < & >')
|
|
assert.equal(result, 'try < & >')
|
|
})
|
|
|
|
test('can format italic text', () => {
|
|
const result = useraccesstokens.expand('This is *italic* text')
|
|
assert.equal(result, 'This is <i>italic</i> text')
|
|
})
|
|
})
|
|
|
|
suite('server TokenManager', async () => {
|
|
let tempDir
|
|
let tokenManager
|
|
|
|
// Realistic user objects that match what OAuth2/Passport provide
|
|
const testUser1 = {
|
|
displayName: 'John Doe',
|
|
email: 'john@example.com',
|
|
provider: 'github',
|
|
id: '12345'
|
|
}
|
|
|
|
const testUser2 = {
|
|
displayName: 'Jane Smith',
|
|
email: 'jane@example.com',
|
|
provider: 'google',
|
|
id: '67890'
|
|
}
|
|
|
|
// Same user object but different reference (tests object equality)
|
|
const testUser1Copy = {
|
|
displayName: 'John Doe',
|
|
email: 'john@example.com',
|
|
provider: 'github',
|
|
id: '12345'
|
|
}
|
|
|
|
// Setup before each test
|
|
const setup = async () => {
|
|
tempDir = path.join(__dirname, 'temp-' + Date.now())
|
|
await fs.mkdir(tempDir, { recursive: true })
|
|
tokenManager = new (class extends TokenManager {
|
|
constructor(statusPath) {
|
|
super(statusPath)
|
|
}
|
|
})(tempDir)
|
|
}
|
|
|
|
// Cleanup after each test
|
|
const cleanup = async () => {
|
|
if (tempDir) {
|
|
await fs.rm(tempDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
test('can generate tokens with correct prefix', async () => {
|
|
await setup()
|
|
try {
|
|
const token = tokenManager.generateToken()
|
|
assert(token.startsWith('fwuat-'))
|
|
assert(token.length > 20) // Should be reasonably long
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('can create and validate tokens', async () => {
|
|
await setup()
|
|
try {
|
|
const result = await tokenManager.createToken(testUser1, 'test-token')
|
|
|
|
assert(result.token.startsWith('fwuat-'))
|
|
assert.equal(result.record.name, 'test-token')
|
|
assert.deepEqual(result.record.user, testUser1)
|
|
assert.equal(result.record.revoked, false)
|
|
assert(result.record.created)
|
|
assert(result.record.displayHint)
|
|
|
|
const validation = await tokenManager.validateToken(result.token)
|
|
assert(validation)
|
|
assert.deepEqual(validation.user, testUser1)
|
|
assert.equal(validation.name, 'test-token')
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('can list user tokens safely', async () => {
|
|
await setup()
|
|
try {
|
|
await tokenManager.createToken(testUser1, 'token1')
|
|
await tokenManager.createToken(testUser1, 'token2')
|
|
await tokenManager.createToken(testUser2, 'token3')
|
|
|
|
const user1Tokens = await tokenManager.listTokens(testUser1)
|
|
assert.equal(user1Tokens.length, 2)
|
|
|
|
// Verify tokenHash is not included in the response
|
|
user1Tokens.forEach(token => {
|
|
assert(!token.tokenHash)
|
|
assert(token.displayHint)
|
|
assert.deepEqual(token.user, testUser1)
|
|
})
|
|
|
|
const user2Tokens = await tokenManager.listTokens(testUser2)
|
|
assert.equal(user2Tokens.length, 1)
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('can revoke tokens', async () => {
|
|
await setup()
|
|
try {
|
|
const result = await tokenManager.createToken(testUser1, 'test-token')
|
|
|
|
// Token should work before revocation
|
|
let validation = await tokenManager.validateToken(result.token)
|
|
assert(validation)
|
|
|
|
// Revoke the token
|
|
await tokenManager.revokeToken(testUser1, 'test-token')
|
|
|
|
// Token should not work after revocation
|
|
validation = await tokenManager.validateToken(result.token)
|
|
assert.equal(validation, null)
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('rejects duplicate token names for same user', async () => {
|
|
await setup()
|
|
try {
|
|
await tokenManager.createToken(testUser1, 'duplicate-name')
|
|
|
|
try {
|
|
await tokenManager.createToken(testUser1, 'duplicate-name')
|
|
assert.fail('Should have thrown an error for duplicate name')
|
|
} catch (error) {
|
|
assert(error.message.includes('already exists'))
|
|
}
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('handles token expiration', async () => {
|
|
await setup()
|
|
try {
|
|
// Create token that expires immediately (for testing)
|
|
const result = await tokenManager.createToken(testUser1, 'expired-token', -1)
|
|
|
|
// Token should be expired and not validate
|
|
const validation = await tokenManager.validateToken(result.token)
|
|
assert.equal(validation, null)
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('correctly handles object equality for user comparisons', async () => {
|
|
await setup()
|
|
try {
|
|
// Create token with testUser1
|
|
const result = await tokenManager.createToken(testUser1, 'test-token')
|
|
|
|
// List tokens using testUser1Copy (same content, different object reference)
|
|
const tokens = await tokenManager.listTokens(testUser1Copy)
|
|
assert.equal(tokens.length, 1)
|
|
assert.equal(tokens[0].name, 'test-token')
|
|
|
|
// Revoke token using testUser1Copy
|
|
await tokenManager.revokeToken(testUser1Copy, 'test-token')
|
|
|
|
// Verify token is revoked
|
|
const validation = await tokenManager.validateToken(result.token)
|
|
assert.equal(validation, null)
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
|
|
test('can delete tokens with object user equality', async () => {
|
|
await setup()
|
|
try {
|
|
await tokenManager.createToken(testUser1, 'token-to-delete')
|
|
|
|
// Verify token exists
|
|
let tokens = await tokenManager.listTokens(testUser1)
|
|
assert.equal(tokens.length, 1)
|
|
|
|
// Delete using testUser1Copy (same content, different reference)
|
|
await tokenManager.deleteToken(testUser1Copy, 'token-to-delete')
|
|
|
|
// Verify token is gone
|
|
tokens = await tokenManager.listTokens(testUser1)
|
|
assert.equal(tokens.length, 0)
|
|
} finally {
|
|
await cleanup()
|
|
}
|
|
})
|
|
})
|
|
})
|