@@ -10,7 +10,18 @@ import { sendTeamsNotifications } from '@/lib/util'
1010import { PrismaAdapter } from '@auth/prisma-adapter'
1111import { prisma } from '@klicker-uzh/prisma'
1212import { UserLoginScope , UserRole } from '@klicker-uzh/prisma/client'
13- import { JWTPayload , signJWT , verifyJWT } from '@klicker-uzh/util'
13+ import {
14+ collectAllEmails ,
15+ deriveCookieDomainFromURL ,
16+ extractProviderFromAffiliationId ,
17+ generateRandomString ,
18+ JWTPayload ,
19+ parseCookiesHeader ,
20+ parseCsvHosts ,
21+ reduceCatalyst ,
22+ signJWT ,
23+ verifyJWT ,
24+ } from '@klicker-uzh/util'
1425import bcrypt from 'bcryptjs'
1526import crypto from 'crypto'
1627import type { NextApiRequest , NextApiResponse } from 'next'
@@ -28,31 +39,6 @@ if (!process.env.APP_ORIGIN_AUTH) {
2839
2940// Context detection: prefer explicit URL params and paths; fall back to
3041// referer and an ephemeral redirect cookie set by middleware on signin.
31- function parseCookies ( req : NextApiRequest ) : Record < string , string > {
32- const cookieHeader = req . headers . cookie || ''
33- const map : Record < string , string > = { }
34- cookieHeader . split ( ';' ) . forEach ( ( part ) => {
35- const [ rawKey , ...rawVal ] = part . split ( '=' )
36- if ( ! rawKey ) return
37- const key = rawKey . trim ( )
38- const value = rawVal . join ( '=' ) . trim ( )
39- if ( ! key ) return
40- try {
41- map [ key ] = decodeURIComponent ( value )
42- } catch {
43- map [ key ] = value
44- }
45- } )
46- return map
47- }
48-
49- function parseCsvHosts ( value ?: string | null ) : string [ ] {
50- if ( ! value ) return [ ]
51- return value
52- . split ( ',' )
53- . map ( ( s ) => s . trim ( ) )
54- . filter ( Boolean )
55- }
5642
5743function getStudentHosts ( ) : string [ ] {
5844 const env = parseCsvHosts ( process . env . AUTH_STUDENT_ALLOWED_HOSTS )
@@ -79,7 +65,7 @@ function getAuthContext(
7965 participant ?: string
8066 callbackUrl ?: string
8167 }
82- const cookies = parseCookies ( req )
68+ const cookies = parseCookiesHeader ( req . headers . cookie )
8369 const studentRedirect = cookies [ STUDENT_REDIRECT_COOKIE_NAME ]
8470 const lecturerRedirect = cookies [ LECTURER_REDIRECT_COOKIE_NAME ]
8571
@@ -161,22 +147,6 @@ export interface ExtendedUser {
161147 catalystIndividual : boolean
162148}
163149
164- function reduceCatalyst ( acc : boolean , affiliation : string ) {
165- try {
166- const parts = affiliation . split ( '@' )
167- if ( parts . length < 2 ) return acc || false
168-
169- const domain = parts [ 1 ]
170- if ( domain ?. includes ( 'uzh.ch' ) || domain ?. includes ( 'usz.ch' ) ) {
171- return true
172- }
173-
174- return acc || false
175- } catch ( e ) {
176- return false
177- }
178- }
179-
180150export async function decode ( { token, secret } : JWTDecodeParams ) {
181151 if ( ! token ) return null
182152 const secretString = typeof secret === 'string' ? secret : secret . toString ( )
@@ -191,53 +161,9 @@ export async function encode({ token, secret }: JWTEncodeParams) {
191161 } )
192162}
193163
194- function extractProviderFromAffiliationId (
195- affiliationId : string
196- ) : string | null {
197- try {
198- const parts = affiliationId . split ( '@' )
199- if ( parts . length < 2 ) return null
164+ // extractProviderFromAffiliationId moved to @klicker -uzh/util
200165
201- const domainParts = parts [ 1 ] ?. split ( '.' )
202- if ( ! domainParts || domainParts . length === 0 ) return null
203-
204- const provider = domainParts [ 0 ]
205- return provider || null
206- } catch {
207- return null
208- }
209- }
210-
211- function collectAllEmails (
212- primaryEmail ?: string ,
213- affiliationEmails ?: string [ ]
214- ) : string [ ] {
215- const emails = [ ]
216- if ( primaryEmail ) emails . push ( primaryEmail . toLowerCase ( ) )
217- if ( affiliationEmails ) {
218- emails . push ( ...affiliationEmails . map ( ( email ) => email . toLowerCase ( ) ) )
219- }
220- return emails . filter ( Boolean )
221- }
222-
223- function generateRandomString ( length : number ) {
224- let result = ''
225- let characters
226- for ( let i = 0 ; i < length ; i ++ ) {
227- if ( i === 0 || i === length - 1 ) {
228- characters =
229- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
230- } else {
231- // TODO: re-introduce allowance for hyphens and underscores again when they are fully supported by manipulation forms
232- characters =
233- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
234- // 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
235- }
236- const charactersLength = characters . length
237- result += characters . charAt ( Math . floor ( Math . random ( ) * charactersLength ) )
238- }
239- return result
240- }
166+ // generateRandomString moved to @klicker -uzh/util
241167
242168async function autoAcceptInvitations ( emails : string [ ] , participantId ?: string ) {
243169 let matchingParticipantId : string | undefined = participantId
@@ -289,11 +215,9 @@ async function autoAcceptInvitations(emails: string[], participantId?: string) {
289215 create : {
290216 courseId : invitation . courseId ,
291217 participantId : matchingParticipantId ! ,
292- isActive : true ,
293- } ,
294- update : {
295- isActive : true ,
218+ isActive : false ,
296219 } ,
220+ update : { } ,
297221 } )
298222
299223 // Mark invitation as accepted
@@ -577,20 +501,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
577501 // label from the NEXTAUTH_URL hostname (e.g., auth.klicker.com -> klicker.com).
578502 // Avoid setting Domain for localhost or IPs.
579503 const cookieDomain : string | undefined = ( ( ) => {
580- try {
581- if ( ! process . env . NEXTAUTH_URL ) return undefined
582- const hostname = new URL ( process . env . NEXTAUTH_URL ) . hostname
583- if ( hostname === 'localhost' || / ^ \d + \. \d + \. \d + \. \d + $ / . test ( hostname ) ) {
584- return undefined
585- }
586- const parts = hostname . split ( '.' )
587- if ( parts . length < 2 ) return undefined
588- parts . shift ( )
589- if ( parts . length < 2 ) return undefined
590- return parts . join ( '.' )
591- } catch {
592- return undefined
593- }
504+ return deriveCookieDomainFromURL ( process . env . NEXTAUTH_URL )
594505 } ) ( )
595506
596507 let sharedOptions : Partial < NextAuthOptions > = {
0 commit comments