()
| 5955 | } |
| 5956 | |
| 5957 | private setupAuthRoutes(): void { |
| 5958 | if (!workos) { |
| 5959 | logger.error('Cannot setup auth routes - WorkOS not initialized'); |
| 5960 | return; |
| 5961 | } |
| 5962 | |
| 5963 | const orgDb = new OrganizationDatabase(); |
| 5964 | |
| 5965 | // GET /auth/login - Redirect to WorkOS for authentication (or dev login page) |
| 5966 | // On AdCP domain, redirect to AAO first to keep auth on a single domain |
| 5967 | // Supports slack_user_id param for auto-linking after login (for existing users) |
| 5968 | this.app.get('/auth/login', (req, res) => { |
| 5969 | try { |
| 5970 | // Dev mode: show dev login page |
| 5971 | if (isDevModeEnabled()) { |
| 5972 | const returnTo = req.query.return_to as string || '/member-hub'; |
| 5973 | return res.redirect(`/dev-login.html?return_to=${encodeURIComponent(returnTo)}`); |
| 5974 | } |
| 5975 | |
| 5976 | // If on AdCP domain, redirect to AAO for login (keeps cookies on single domain) |
| 5977 | // Preserve the AdCP URL as return_to so the session bridge sends them back to AdCP after login |
| 5978 | if (this.isAdcpDomain(req)) { |
| 5979 | const returnTo = req.query.return_to as string; |
| 5980 | const slackUserId = req.query.slack_user_id as string; |
| 5981 | // Keep the return_to as an AdCP URL so the callback bridges the session back |
| 5982 | let aaoReturnTo = returnTo; |
| 5983 | if (returnTo && returnTo.startsWith('/')) { |
| 5984 | aaoReturnTo = `https://${req.get('host')}${returnTo}`; |
| 5985 | } |
| 5986 | let redirectUrl = 'https://agenticadvertising.org/auth/login'; |
| 5987 | const params = new URLSearchParams(); |
| 5988 | if (aaoReturnTo) params.append('return_to', aaoReturnTo); |
| 5989 | if (slackUserId) params.append('slack_user_id', slackUserId); |
| 5990 | if (params.toString()) redirectUrl += `?${params.toString()}`; |
| 5991 | return res.redirect(redirectUrl); |
| 5992 | } |
| 5993 | |
| 5994 | const returnTo = req.query.return_to as string; |
| 5995 | const slackUserId = req.query.slack_user_id as string; |
| 5996 | const nativeMode = req.query.native === 'true'; |
| 5997 | const nativeRedirectUri = req.query.redirect_uri as string; |
| 5998 | |
| 5999 | // Validate native redirect URI to prevent open redirect attacks |
| 6000 | const ALLOWED_NATIVE_SCHEMES = ['addie://']; |
| 6001 | const isValidNativeRedirectUri = (uri: string): boolean => { |
| 6002 | return ALLOWED_NATIVE_SCHEMES.some(scheme => uri.startsWith(scheme)); |
| 6003 | }; |
| 6004 | |
| 6005 | if (nativeMode && nativeRedirectUri && !isValidNativeRedirectUri(nativeRedirectUri)) { |
| 6006 | return res.status(400).json({ error: 'Invalid redirect_uri - must use addie:// scheme' }); |
| 6007 | } |
| 6008 | |
| 6009 | // Build state object with return_to, slack_user_id for auto-linking, and native app params |
| 6010 | const stateObj: { return_to?: string; slack_user_id?: string; native?: boolean; native_redirect_uri?: string } = {}; |
| 6011 | if (returnTo) stateObj.return_to = returnTo; |
| 6012 | if (slackUserId) stateObj.slack_user_id = slackUserId; |
| 6013 | if (nativeMode) { |
| 6014 | stateObj.native = true; |
no test coverage detected