Make it work.
This commit is contained in:
205
test/middleware-integration.test.js
Normal file
205
test/middleware-integration.test.js
Normal file
@ -0,0 +1,205 @@
|
||||
import { suite, test } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { TokenManager } from '../server/server.js'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
// Import the securityEnhancer to test middleware
|
||||
import { securityEnhancer } from '../index.js'
|
||||
|
||||
suite('Bearer token middleware integration', () => {
|
||||
let tempDir
|
||||
let tokenManager
|
||||
let middleware
|
||||
let testToken
|
||||
let testUser
|
||||
|
||||
const setup = async () => {
|
||||
tempDir = path.join(__dirname, 'temp-integration-' + Date.now())
|
||||
await fs.mkdir(tempDir, { recursive: true })
|
||||
|
||||
tokenManager = new TokenManager(tempDir)
|
||||
testUser = {
|
||||
displayName: 'Test User',
|
||||
email: 'test@example.com',
|
||||
provider: 'github',
|
||||
id: '12345'
|
||||
}
|
||||
|
||||
// Create a test token
|
||||
const result = await tokenManager.createToken(testUser, 'test-token')
|
||||
testToken = result.token
|
||||
|
||||
// Create the enhancer and get middleware
|
||||
const mockLog = console.log
|
||||
const mockLoga = console.log
|
||||
const mockArgv = { status: tempDir }
|
||||
const mockBaseHandler = {
|
||||
getUser: () => null,
|
||||
isAuthorized: () => false
|
||||
}
|
||||
|
||||
const enhancer = securityEnhancer(mockLog, mockLoga, mockArgv, mockBaseHandler)
|
||||
middleware = enhancer.middleware
|
||||
}
|
||||
|
||||
const cleanup = async () => {
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
test('middleware works with standard Bearer header', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const req = {
|
||||
headers: {
|
||||
authorization: `Bearer ${testToken}`
|
||||
}
|
||||
}
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(req.tokenAuth)
|
||||
assert.deepEqual(req.tokenAuth.user, testUser)
|
||||
assert.equal(req.tokenAuth.tokenName, 'test-token')
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('middleware works with case-insensitive headers', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const testCases = [
|
||||
{ authorization: `Bearer ${testToken}` },
|
||||
{ Authorization: `Bearer ${testToken}` },
|
||||
{ AUTHORIZATION: `Bearer ${testToken}` },
|
||||
{ AuThOrIzAtIoN: `Bearer ${testToken}` }
|
||||
]
|
||||
|
||||
for (const headers of testCases) {
|
||||
const req = { headers }
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(req.tokenAuth, `Failed with headers: ${JSON.stringify(headers)}`)
|
||||
assert.deepEqual(req.tokenAuth.user, testUser)
|
||||
}
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('middleware works with case-insensitive Bearer scheme', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const testCases = [
|
||||
`Bearer ${testToken}`,
|
||||
`bearer ${testToken}`,
|
||||
`BEARER ${testToken}`,
|
||||
`BeArEr ${testToken}`
|
||||
]
|
||||
|
||||
for (const authValue of testCases) {
|
||||
const req = {
|
||||
headers: { authorization: authValue }
|
||||
}
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(req.tokenAuth, `Failed with auth: ${authValue}`)
|
||||
assert.deepEqual(req.tokenAuth.user, testUser)
|
||||
}
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('middleware works with various whitespace patterns', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const testCases = [
|
||||
`Bearer ${testToken}`,
|
||||
` Bearer ${testToken}`,
|
||||
` Bearer ${testToken}`,
|
||||
`\tBearer ${testToken}`,
|
||||
`Bearer ${testToken}`,
|
||||
`Bearer ${testToken}`,
|
||||
`Bearer\t${testToken}`,
|
||||
` Bearer ${testToken} `,
|
||||
`\tBEARER\t\t${testToken}\t`
|
||||
]
|
||||
|
||||
for (const authValue of testCases) {
|
||||
const req = {
|
||||
headers: { authorization: authValue }
|
||||
}
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(req.tokenAuth, `Failed with auth: "${authValue}"`)
|
||||
assert.deepEqual(req.tokenAuth.user, testUser)
|
||||
}
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('middleware rejects invalid schemes and formats', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const testCases = [
|
||||
`Basic ${testToken}`,
|
||||
`Digest ${testToken}`,
|
||||
`OAuth ${testToken}`,
|
||||
`Bearer`,
|
||||
`Bearerabc123`,
|
||||
``,
|
||||
`NotBearer ${testToken}`,
|
||||
`Bearer${testToken}` // No space
|
||||
]
|
||||
|
||||
for (const authValue of testCases) {
|
||||
const req = {
|
||||
headers: { authorization: authValue }
|
||||
}
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(!req.tokenAuth, `Should have failed with auth: "${authValue}"`)
|
||||
}
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('middleware handles missing authorization header gracefully', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const req = { headers: {} }
|
||||
const res = {}
|
||||
const next = () => {}
|
||||
|
||||
await middleware(req, res, next)
|
||||
|
||||
assert(!req.tokenAuth)
|
||||
} finally {
|
||||
await cleanup()
|
||||
}
|
||||
})
|
||||
})
|
||||
93
test/test.js
93
test/test.js
@ -26,6 +26,29 @@ suite('useraccesstokens plugin', () => {
|
||||
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())
|
||||
@ -58,18 +81,18 @@ suite('useraccesstokens plugin', () => {
|
||||
test('can create and validate tokens', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const result = await tokenManager.createToken('testuser', 'test-token')
|
||||
const result = await tokenManager.createToken(testUser1, 'test-token')
|
||||
|
||||
assert(result.token.startsWith('fwuat-'))
|
||||
assert.equal(result.record.name, 'test-token')
|
||||
assert.equal(result.record.user, 'testuser')
|
||||
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.equal(validation.user, 'testuser')
|
||||
assert.deepEqual(validation.user, testUser1)
|
||||
assert.equal(validation.name, 'test-token')
|
||||
} finally {
|
||||
await cleanup()
|
||||
@ -79,21 +102,21 @@ suite('useraccesstokens plugin', () => {
|
||||
test('can list user tokens safely', async () => {
|
||||
await setup()
|
||||
try {
|
||||
await tokenManager.createToken('user1', 'token1')
|
||||
await tokenManager.createToken('user1', 'token2')
|
||||
await tokenManager.createToken('user2', 'token3')
|
||||
await tokenManager.createToken(testUser1, 'token1')
|
||||
await tokenManager.createToken(testUser1, 'token2')
|
||||
await tokenManager.createToken(testUser2, 'token3')
|
||||
|
||||
const user1Tokens = await tokenManager.listTokens('user1')
|
||||
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.equal(token.user, 'user1')
|
||||
assert.deepEqual(token.user, testUser1)
|
||||
})
|
||||
|
||||
const user2Tokens = await tokenManager.listTokens('user2')
|
||||
const user2Tokens = await tokenManager.listTokens(testUser2)
|
||||
assert.equal(user2Tokens.length, 1)
|
||||
} finally {
|
||||
await cleanup()
|
||||
@ -103,14 +126,14 @@ suite('useraccesstokens plugin', () => {
|
||||
test('can revoke tokens', async () => {
|
||||
await setup()
|
||||
try {
|
||||
const result = await tokenManager.createToken('testuser', 'test-token')
|
||||
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('testuser', 'test-token')
|
||||
await tokenManager.revokeToken(testUser1, 'test-token')
|
||||
|
||||
// Token should not work after revocation
|
||||
validation = await tokenManager.validateToken(result.token)
|
||||
@ -123,10 +146,10 @@ suite('useraccesstokens plugin', () => {
|
||||
test('rejects duplicate token names for same user', async () => {
|
||||
await setup()
|
||||
try {
|
||||
await tokenManager.createToken('testuser', 'duplicate-name')
|
||||
await tokenManager.createToken(testUser1, 'duplicate-name')
|
||||
|
||||
try {
|
||||
await tokenManager.createToken('testuser', 'duplicate-name')
|
||||
await tokenManager.createToken(testUser1, 'duplicate-name')
|
||||
assert.fail('Should have thrown an error for duplicate name')
|
||||
} catch (error) {
|
||||
assert(error.message.includes('already exists'))
|
||||
@ -140,7 +163,7 @@ suite('useraccesstokens plugin', () => {
|
||||
await setup()
|
||||
try {
|
||||
// Create token that expires immediately (for testing)
|
||||
const result = await tokenManager.createToken('testuser', 'expired-token', -1)
|
||||
const result = await tokenManager.createToken(testUser1, 'expired-token', -1)
|
||||
|
||||
// Token should be expired and not validate
|
||||
const validation = await tokenManager.validateToken(result.token)
|
||||
@ -149,5 +172,47 @@ suite('useraccesstokens plugin', () => {
|
||||
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()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user