Something went wrong. Try again.
AtAuth
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';import express from 'express';import request from 'supertest';import { createUserInfoRouter } from './userinfo.js';import { DatabaseService } from '../../services/database.js';
function createMockOIDCService(accessTokenClaims: any = null) { return { tokenService: { verifyAccessToken: vi.fn().mockReturnValue(accessTokenClaims), verifyIdToken: vi.fn(), createTokenResponse: vi.fn(), }, keyService: { getPublicKeySet: vi.fn() }, } as any;}
function createTestApp(db: DatabaseService, oidcService: any) { const app = express(); app.use(express.json());
const router = createUserInfoRouter(db, oidcService); app.use('/oauth', router);
return app;}
describe('/oauth/userinfo', () => { let db: DatabaseService;
beforeEach(() => { db = new DatabaseService(':memory:'); // Mock the global fetch for AT Protocol API calls vi.spyOn(global, 'fetch').mockResolvedValue({ ok: true, json: async () => ({ handle: 'test.bsky.social' }), } as Response); });
afterEach(() => { db.close(); vi.restoreAllMocks(); });
it('should return 401 for missing Authorization header', async () => { const oidcService = createMockOIDCService(); const app = createTestApp(db, oidcService);
const res = await request(app).get('/oauth/userinfo');
expect(res.status).toBe(401); expect(res.body.error).toBe('invalid_token'); });
it('should return 401 for non-Bearer auth', async () => { const oidcService = createMockOIDCService(); const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Basic abc123');
expect(res.status).toBe(401); expect(res.body.error).toBe('invalid_token'); });
it('should return 401 for invalid access token', async () => { const oidcService = createMockOIDCService(null); // null = invalid token const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Bearer invalid-token');
expect(res.status).toBe(401); expect(res.body.error).toBe('invalid_token'); expect(res.body.error_description).toContain('expired'); });
it('should return user info for valid token with openid scope', async () => { const oidcService = createMockOIDCService({ sub: 'did:plc:testuser', scope: 'openid', client_id: 'test-client', }); const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Bearer valid-token');
expect(res.status).toBe(200); expect(res.body.sub).toBe('did:plc:testuser'); });
it('should return profile claims for profile scope', async () => { const oidcService = createMockOIDCService({ sub: 'did:plc:testuser', scope: 'openid profile', client_id: 'test-client', }); const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Bearer valid-token');
expect(res.status).toBe(200); expect(res.body.sub).toBe('did:plc:testuser'); expect(res.body.preferred_username).toBe('test.bsky.social'); });
it('should support POST method', async () => { const oidcService = createMockOIDCService({ sub: 'did:plc:testuser', scope: 'openid', client_id: 'test-client', }); const app = createTestApp(db, oidcService);
const res = await request(app) .post('/oauth/userinfo') .set('Authorization', 'Bearer valid-token');
expect(res.status).toBe(200); expect(res.body.sub).toBe('did:plc:testuser'); });
it('should use user mapping handle when available', async () => { const oidcService = createMockOIDCService({ sub: 'did:plc:testuser', scope: 'openid profile', client_id: 'test-app', });
// Register app and create user mapping db.upsertApp({ id: 'test-app', name: 'Test', hmac_secret: 'a'.repeat(64), token_ttl_seconds: 3600, }); db.setUserMapping({ did: 'did:plc:testuser', app_id: 'test-app', user_id: 1, handle: 'mapped.handle.social', });
const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Bearer valid-token');
expect(res.status).toBe(200); expect(res.body.preferred_username).toBe('mapped.handle.social'); // Should NOT have called fetch since mapping was found expect(global.fetch).not.toHaveBeenCalled(); });
it('should fall back to DID when API call fails', async () => { vi.spyOn(global, 'fetch').mockRejectedValue(new Error('network error'));
const oidcService = createMockOIDCService({ sub: 'did:plc:testuser', scope: 'openid profile', client_id: 'test-client', }); const app = createTestApp(db, oidcService);
const res = await request(app) .get('/oauth/userinfo') .set('Authorization', 'Bearer valid-token');
expect(res.status).toBe(200); expect(res.body.preferred_username).toBe('did:plc:testuser'); });});