fix(tokens): tighten machine credential issuance
This commit is contained in:
parent
57abfa5b03
commit
473b7310d5
@ -129,9 +129,7 @@ describe('tokenRoutes', () => {
|
|||||||
expect(repoMock.hashToken).toHaveBeenCalled();
|
expect(repoMock.hashToken).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('POST /tokens respects explicit environment overrides', async () => {
|
it('POST /tokens rejects unsupported user_api tokens', async () => {
|
||||||
repoMock.hashToken.mockResolvedValue('hashed_token');
|
|
||||||
repoMock.create.mockResolvedValue(baseToken);
|
|
||||||
const app = await buildApp({
|
const app = await buildApp({
|
||||||
sub: 'admin_1',
|
sub: 'admin_1',
|
||||||
productId: 'lysnrai',
|
productId: 'lysnrai',
|
||||||
@ -139,6 +137,83 @@ describe('tokenRoutes', () => {
|
|||||||
email: 'admin@example.com',
|
email: 'admin@example.com',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/tokens',
|
||||||
|
payload: {
|
||||||
|
name: 'legacy-user-token',
|
||||||
|
tokenType: 'user_api',
|
||||||
|
scopes: ['read'],
|
||||||
|
expiresInDays: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
expect(repoMock.create).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('POST /tokens rejects service_api creation for non-super-admins', async () => {
|
||||||
|
const app = await buildApp({
|
||||||
|
sub: 'admin_1',
|
||||||
|
productId: 'lysnrai',
|
||||||
|
role: 'admin',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/tokens',
|
||||||
|
payload: {
|
||||||
|
name: 'service-token',
|
||||||
|
tokenType: 'service_api',
|
||||||
|
scopes: ['jobs:read'],
|
||||||
|
expiresInDays: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(403);
|
||||||
|
expect(repoMock.create).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('POST /tokens allows service_api creation for super_admin', async () => {
|
||||||
|
repoMock.hashToken.mockResolvedValue('hashed_token');
|
||||||
|
repoMock.create.mockResolvedValue(baseToken);
|
||||||
|
const app = await buildApp({
|
||||||
|
sub: 'root_1',
|
||||||
|
productId: 'lysnrai',
|
||||||
|
role: 'super_admin',
|
||||||
|
email: 'root@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/tokens',
|
||||||
|
payload: {
|
||||||
|
name: 'service-token',
|
||||||
|
tokenType: 'service_api',
|
||||||
|
scopes: ['jobs:read'],
|
||||||
|
expiresInDays: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(201);
|
||||||
|
expect(repoMock.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
tokenType: 'service_api',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('POST /tokens respects explicit environment overrides', async () => {
|
||||||
|
repoMock.hashToken.mockResolvedValue('hashed_token');
|
||||||
|
repoMock.create.mockResolvedValue(baseToken);
|
||||||
|
const app = await buildApp({
|
||||||
|
sub: 'root_1',
|
||||||
|
productId: 'lysnrai',
|
||||||
|
role: 'super_admin',
|
||||||
|
email: 'root@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
const res = await app.inject({
|
const res = await app.inject({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/tokens',
|
url: '/api/tokens',
|
||||||
|
|||||||
@ -69,6 +69,14 @@ export async function tokenRoutes(app: FastifyInstance) {
|
|||||||
const { name, tokenType, environment, scopes, expiresInDays } = parsed.data;
|
const { name, tokenType, environment, scopes, expiresInDays } = parsed.data;
|
||||||
const resolvedEnvironment = environment ?? getRuntimeEnvironment();
|
const resolvedEnvironment = environment ?? getRuntimeEnvironment();
|
||||||
|
|
||||||
|
if (tokenType === 'user_api') {
|
||||||
|
throw new BadRequestError('user_api tokens are not supported for machine credential routes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenType === 'service_api' && payload.role !== 'super_admin') {
|
||||||
|
throw new ForbiddenError('Super admin access required for service_api tokens');
|
||||||
|
}
|
||||||
|
|
||||||
const rawToken = `wai_${crypto.randomBytes(32).toString('hex')}`;
|
const rawToken = `wai_${crypto.randomBytes(32).toString('hex')}`;
|
||||||
const prefix = rawToken.slice(0, 12);
|
const prefix = rawToken.slice(0, 12);
|
||||||
const tokenHash = await repo.hashToken(rawToken);
|
const tokenHash = await repo.hashToken(rawToken);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user