196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
|
|
import prisma from '../../utils/prisma'
|
|
import { keycloak } from '../../utils/keycloak'
|
|
import { protect } from '../auth'
|
|
|
|
const makeUser = (id: number, username: string) => ({
|
|
id,
|
|
username,
|
|
password: 'hidden',
|
|
name: username,
|
|
email: null,
|
|
phone: null,
|
|
role: 'admin',
|
|
status: 'active',
|
|
person_id: null,
|
|
person: null
|
|
})
|
|
|
|
const createResponse = () => {
|
|
const state = { statusCode: 200, body: null as any, nextCount: 0 }
|
|
const res = {
|
|
locals: {} as any,
|
|
status(code: number) {
|
|
state.statusCode = code
|
|
return this
|
|
},
|
|
json(payload: any) {
|
|
state.body = payload
|
|
return this
|
|
}
|
|
}
|
|
const next = () => {
|
|
state.nextCount += 1
|
|
}
|
|
return { res, next, state }
|
|
}
|
|
|
|
test('Keycloak 模式下真实 Bearer 令牌优先于旧的钉钉 H5 session', async () => {
|
|
const originalMode = process.env.AUTH_MODE
|
|
const originalProtect = keycloak.protect
|
|
const originalFindUnique = prisma.user.findUnique
|
|
const lookups: any[] = []
|
|
|
|
process.env.AUTH_MODE = 'keycloak'
|
|
;(keycloak.protect as any) = () => async (req: any, _res: any, next: any) => {
|
|
req.kauth = {
|
|
grant: {
|
|
access_token: {
|
|
content: {
|
|
preferred_username: 'keycloak-user',
|
|
name: 'Keycloak User',
|
|
realm_access: { roles: ['ignored-external-role'] }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return next()
|
|
}
|
|
;(prisma.user.findUnique as any) = async (args: any) => {
|
|
lookups.push(args.where)
|
|
if (args.where.username === 'keycloak-user') return makeUser(22, 'keycloak-user')
|
|
if (args.where.id === 11) return makeUser(11, 'old-dingtalk-session')
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const middleware = protect()
|
|
const req = {
|
|
headers: { authorization: 'Bearer real-keycloak-token' },
|
|
session: { user: { id: 11 } }
|
|
}
|
|
const { res, next, state } = createResponse()
|
|
await middleware(req as any, res as any, next as any)
|
|
|
|
assert.equal(state.nextCount, 1)
|
|
assert.equal(res.locals.user.id, 22)
|
|
assert.deepEqual(lookups, [{ username: 'keycloak-user' }])
|
|
} finally {
|
|
if (originalMode === undefined) delete process.env.AUTH_MODE
|
|
else process.env.AUTH_MODE = originalMode
|
|
;(keycloak.protect as any) = originalProtect
|
|
;(prisma.user.findUnique as any) = originalFindUnique
|
|
}
|
|
})
|
|
|
|
test('Keycloak 模式下 Bearer session 占位值仍使用 H5 session', async () => {
|
|
const originalMode = process.env.AUTH_MODE
|
|
const originalProtect = keycloak.protect
|
|
const originalFindUnique = prisma.user.findUnique
|
|
let keycloakCalled = 0
|
|
|
|
process.env.AUTH_MODE = 'keycloak'
|
|
;(keycloak.protect as any) = () => async (_req: any, _res: any, next: any) => {
|
|
keycloakCalled += 1
|
|
return next()
|
|
}
|
|
;(prisma.user.findUnique as any) = async (args: any) => {
|
|
if (args.where.id === 11) return makeUser(11, 'dingtalk-session')
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const middleware = protect()
|
|
const req = {
|
|
headers: { authorization: 'Bearer session' },
|
|
session: { user: { id: 11 } }
|
|
}
|
|
const { res, next, state } = createResponse()
|
|
await middleware(req as any, res as any, next as any)
|
|
|
|
assert.equal(state.nextCount, 1)
|
|
assert.equal(keycloakCalled, 0)
|
|
assert.equal(res.locals.user.id, 11)
|
|
} finally {
|
|
if (originalMode === undefined) delete process.env.AUTH_MODE
|
|
else process.env.AUTH_MODE = originalMode
|
|
;(keycloak.protect as any) = originalProtect
|
|
;(prisma.user.findUnique as any) = originalFindUnique
|
|
}
|
|
})
|
|
|
|
test('local 模式即使带真实 Bearer 也只使用本地 session', async () => {
|
|
const originalMode = process.env.AUTH_MODE
|
|
const originalFindUnique = prisma.user.findUnique
|
|
|
|
process.env.AUTH_MODE = 'local'
|
|
;(prisma.user.findUnique as any) = async (args: any) => {
|
|
if (args.where.id === 31) return makeUser(31, 'local-session')
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const middleware = protect()
|
|
const req = {
|
|
headers: { authorization: 'Bearer should-not-win' },
|
|
session: { user: { id: 31 } }
|
|
}
|
|
const { res, next, state } = createResponse()
|
|
await middleware(req as any, res as any, next as any)
|
|
|
|
assert.equal(state.nextCount, 1)
|
|
assert.equal(res.locals.user.id, 31)
|
|
} finally {
|
|
if (originalMode === undefined) delete process.env.AUTH_MODE
|
|
else process.env.AUTH_MODE = originalMode
|
|
;(prisma.user.findUnique as any) = originalFindUnique
|
|
}
|
|
})
|
|
|
|
test('钉钉会话在员工改绑后立即失效且不继续读取系统用户', async () => {
|
|
const originalMode = process.env.AUTH_MODE
|
|
const originalFindFirst = prisma.user.findFirst
|
|
const originalFindUnique = prisma.user.findUnique
|
|
let findUniqueCalls = 0
|
|
|
|
process.env.AUTH_MODE = 'local'
|
|
;(prisma.user.findFirst as any) = async (args: any) => {
|
|
assert.equal(args.where.id, 41)
|
|
assert.equal(args.where.person.is.source_userid, 'ding-user-a')
|
|
return null
|
|
}
|
|
;(prisma.user.findUnique as any) = async () => {
|
|
findUniqueCalls += 1
|
|
return makeUser(41, 'rebound-user')
|
|
}
|
|
|
|
try {
|
|
const middleware = protect()
|
|
const req = {
|
|
headers: {},
|
|
session: {
|
|
user: {
|
|
id: 41,
|
|
auth_source: 'dingtalk',
|
|
dingtalk_userid: 'ding-user-a'
|
|
}
|
|
}
|
|
}
|
|
const { res, next, state } = createResponse()
|
|
await middleware(req as any, res as any, next as any)
|
|
|
|
assert.equal(state.nextCount, 0)
|
|
assert.equal(state.statusCode, 401)
|
|
assert.equal(state.body.message, '钉钉绑定已失效,请重新登录或联系管理员')
|
|
assert.equal(req.session.user, undefined)
|
|
assert.equal(findUniqueCalls, 0)
|
|
} finally {
|
|
if (originalMode === undefined) delete process.env.AUTH_MODE
|
|
else process.env.AUTH_MODE = originalMode
|
|
;(prisma.user.findFirst as any) = originalFindFirst
|
|
;(prisma.user.findUnique as any) = originalFindUnique
|
|
}
|
|
})
|