11import request from 'supertest' ;
2+ import jwt from 'jsonwebtoken' ;
23
34import app from '../../../src/app' ;
45import { initPassport } from '../../../src/middleware/passport-auth' ;
56import { ensureWorkerDataSources , resetDatabase } from '../../helpers/reset-database' ;
67import { config } from '../../../src/config' ;
8+ import { dbManager } from '../../../src/db/database-manager' ;
9+ import { UserDTO } from '../../../src/dtos/user/user-dto' ;
10+ import { UserGroup } from '../../../src/entities/user/user-group' ;
11+ import { UserGroupRole } from '../../../src/entities/user/user-group-role' ;
12+ import { GroupRole } from '../../../src/enums/group-role' ;
13+ import { Locale } from '../../../src/enums/locale' ;
14+ import { getTestUser , getTestUserGroup } from '../../helpers/get-test-user' ;
15+ import { getAuthHeader } from '../../helpers/auth-header' ;
716
817// Need to mock blob storage as it is included in services middleware for every route
918// avoids the "Jest did not exit one second after the test run has completed"
@@ -18,6 +27,8 @@ jest.mock('../../../src/services/blob-storage', () => {
1827} ) ;
1928
2029describe ( 'Auth routes' , ( ) => {
30+ const callbackURL = `${ config . frontend . url } /auth/callback` ;
31+
2132 beforeAll ( async ( ) => {
2233 await ensureWorkerDataSources ( ) ;
2334 await resetDatabase ( ) ;
@@ -30,4 +41,102 @@ describe('Auth routes', () => {
3041 expect ( res . status ) . toBe ( 200 ) ;
3142 expect ( res . body ) . toEqual ( { enabled : expectedProviders } ) ;
3243 } ) ;
44+
45+ describe ( 'GET /auth/local (loginLocal)' , ( ) => {
46+ test ( 'redirects to the frontend callback and sets a jwt cookie for a known user' , async ( ) => {
47+ const user = getTestUser ( 'Local Success' ) ;
48+ await user . save ( ) ;
49+
50+ const res = await request ( app ) . get ( '/auth/local' ) . query ( { username : user . providerUserId } ) ;
51+
52+ expect ( res . status ) . toBe ( 302 ) ;
53+ expect ( res . headers . location ) . toBe ( callbackURL ) ;
54+
55+ const cookies = res . headers [ 'set-cookie' ] as unknown as string [ ] ;
56+ const jwtCookie = cookies . find ( ( cookie ) => cookie . startsWith ( 'jwt=' ) ) ;
57+ expect ( jwtCookie ) . toBeDefined ( ) ;
58+ expect ( jwtCookie ) . toContain ( 'HttpOnly' ) ;
59+ } ) ;
60+
61+ test ( 'redirects with error=login when no username is provided' , async ( ) => {
62+ const res = await request ( app ) . get ( '/auth/local' ) ;
63+ expect ( res . status ) . toBe ( 302 ) ;
64+ expect ( res . headers . location ) . toBe ( `${ callbackURL } ?error=login` ) ;
65+ expect ( res . headers [ 'set-cookie' ] ) . toBeUndefined ( ) ;
66+ } ) ;
67+
68+ test ( 'redirects with error=login when the user does not exist' , async ( ) => {
69+ const res = await request ( app ) . get ( '/auth/local' ) . query ( { username : 'no-such-user' } ) ;
70+ expect ( res . status ) . toBe ( 302 ) ;
71+ expect ( res . headers . location ) . toBe ( `${ callbackURL } ?error=login` ) ;
72+ expect ( res . headers [ 'set-cookie' ] ) . toBeUndefined ( ) ;
73+ } ) ;
74+ } ) ;
75+
76+ // Exercises the JWT strategy branches in passport-auth.ts via the protected /healthcheck/jwt probe.
77+ // healthcheck.test.ts keeps a single 200 case as a smoke test of the route itself.
78+ describe ( 'JWT auth middleware (passport-auth)' , ( ) => {
79+ test ( '/healthcheck/jwt returns 401 without a bearer token' , async ( ) => {
80+ const res = await request ( app ) . get ( '/healthcheck/jwt' ) ;
81+ expect ( res . status ) . toBe ( 401 ) ;
82+ } ) ;
83+
84+ test ( '/healthcheck/jwt returns 401 with an invalid bearer token' , async ( ) => {
85+ const res = await request ( app ) . get ( '/healthcheck/jwt' ) . set ( { Authorization : 'Bearer this-is-not-a-token' } ) ;
86+ expect ( res . status ) . toBe ( 401 ) ;
87+ } ) ;
88+
89+ test ( '/healthcheck/jwt returns 401 with a valid token for a user that does not exist' , async ( ) => {
90+ const unknownUser = getTestUser ( 'Unknown JWT User' ) ;
91+ const res = await request ( app ) . get ( '/healthcheck/jwt' ) . set ( getAuthHeader ( unknownUser ) ) ;
92+ expect ( res . status ) . toBe ( 401 ) ;
93+ } ) ;
94+
95+ test ( '/healthcheck/jwt returns 401 for an expired token' , async ( ) => {
96+ const user = getTestUser ( 'Expired Token User' ) ;
97+ await user . save ( ) ;
98+
99+ const token = jwt . sign ( { user : UserDTO . fromUser ( user , Locale . English ) } , config . auth . jwt . secret , {
100+ expiresIn : '-1s'
101+ } ) ;
102+
103+ const res = await request ( app )
104+ . get ( '/healthcheck/jwt' )
105+ . set ( { Authorization : `Bearer ${ token } ` } ) ;
106+ expect ( res . status ) . toBe ( 401 ) ;
107+ } ) ;
108+
109+ test ( '/healthcheck/jwt returns 401 when the user permissions have changed since the token was issued' , async ( ) => {
110+ const group = await dbManager
111+ . getPublisherDataSource ( )
112+ . getRepository ( UserGroup )
113+ . save ( getTestUserGroup ( 'Perm Change Group' ) ) ;
114+
115+ const user = getTestUser ( 'Perm Change User' ) ;
116+ user . groupRoles = [ UserGroupRole . create ( { group, roles : [ GroupRole . Editor ] } ) ] ;
117+ await user . save ( ) ;
118+
119+ // token captures the user while they hold the Editor role
120+ const authHeader = getAuthHeader ( user ) ;
121+
122+ // revoke the role in the database so the live permissions no longer match the token
123+ await dbManager . getPublisherDataSource ( ) . getRepository ( UserGroupRole ) . delete ( { userId : user . id } ) ;
124+
125+ const res = await request ( app ) . get ( '/healthcheck/jwt' ) . set ( authHeader ) ;
126+ expect ( res . status ) . toBe ( 401 ) ;
127+ } ) ;
128+
129+ test ( '/healthcheck/jwt returns 200 with a valid bearer token' , async ( ) => {
130+ const user = getTestUser ( 'Valid JWT User' ) ;
131+ await user . save ( ) ;
132+
133+ const res = await request ( app ) . get ( '/healthcheck/jwt' ) . set ( getAuthHeader ( user ) ) ;
134+
135+ expect ( res . status ) . toBe ( 200 ) ;
136+ expect ( res . body ) . toEqual ( {
137+ message : 'success' ,
138+ user : UserDTO . fromUser ( user , Locale . English )
139+ } ) ;
140+ } ) ;
141+ } ) ;
33142} ) ;
0 commit comments