@@ -23,54 +23,19 @@ export default class SamlController {
2323 */
2424 private factories : ContextFactories ;
2525
26+ /**
27+ * SAML controller constructor used for DI
28+ * @param factories - for working with models
29+ */
2630 constructor ( factories : ContextFactories ) {
2731 this . samlService = new SamlService ( ) ;
2832 this . factories = factories ;
2933 }
3034
31- /**
32- * Log message with SSO prefix
33- *
34- * @param level - log level ('log', 'warn', 'error', 'info', 'success')
35- * @param args - arguments to log
36- */
37- private log ( level : 'log' | 'warn' | 'error' | 'info' | 'success' , ...args : unknown [ ] ) : void {
38- const colors = {
39- log : Effect . ForegroundGreen ,
40- warn : Effect . ForegroundYellow ,
41- error : Effect . ForegroundRed ,
42- info : Effect . ForegroundBlue ,
43- success : [ Effect . ForegroundGreen , Effect . Bold ] ,
44- } ;
45-
46- const logger = level === 'error' ? console . error : level === 'warn' ? console . warn : console . log ;
47-
48- logger ( sgr ( '[SSO]' , colors [ level ] ) , ...args ) ;
49- }
50-
51- /**
52- * Validate workspace ID format
53- *
54- * @param workspaceId - workspace ID to validate
55- * @returns true if valid, false otherwise
56- */
57- private isValidWorkspaceId ( workspaceId : string ) : boolean {
58- return ObjectId . isValid ( workspaceId ) ;
59- }
60-
61- /**
62- * Compose Assertion Consumer Service URL for workspace
63- *
64- * @param workspaceId - workspace ID
65- * @returns ACS URL
66- */
67- private getAcsUrl ( workspaceId : string ) : string {
68- const apiUrl = process . env . API_URL || 'https://api.hawk.so' ;
69- return `${ apiUrl } /auth/sso/saml/${ workspaceId } /acs` ;
70- }
71-
7235 /**
7336 * Initiate SSO login (GET /auth/sso/saml/:workspaceId)
37+ * @param req - Express request
38+ * @param res - Express response
7439 */
7540 public async initiateLogin ( req : express . Request , res : express . Response ) : Promise < void > {
7641 const { workspaceId } = req . params ;
@@ -84,6 +49,7 @@ export default class SamlController {
8449 if ( ! this . isValidWorkspaceId ( workspaceId ) ) {
8550 this . log ( 'warn' , 'Invalid workspace ID format:' , sgr ( workspaceId , Effect . ForegroundRed ) ) ;
8651 res . status ( 400 ) . json ( { error : 'Invalid workspace ID' } ) ;
52+
8753 return ;
8854 }
8955
@@ -95,6 +61,7 @@ export default class SamlController {
9561 if ( ! workspace || ! workspace . sso ?. enabled ) {
9662 this . log ( 'warn' , 'SSO not enabled for workspace:' , sgr ( workspaceId , Effect . ForegroundCyan ) ) ;
9763 res . status ( 400 ) . json ( { error : 'SSO is not enabled for this workspace' } ) ;
64+
9865 return ;
9966 }
10067
@@ -107,7 +74,10 @@ export default class SamlController {
10774 /**
10875 * 3. Save RelayState to temporary storage
10976 */
110- samlStore . saveRelayState ( relayStateId , { returnUrl, workspaceId } ) ;
77+ samlStore . saveRelayState ( relayStateId , {
78+ returnUrl,
79+ workspaceId,
80+ } ) ;
11181
11282 /**
11383 * 4. Generate AuthnRequest
@@ -141,6 +111,7 @@ export default class SamlController {
141111 * 6. Redirect to IdP
142112 */
143113 const redirectUrl = new URL ( workspace . sso . saml . ssoUrl ) ;
114+
144115 redirectUrl . searchParams . set ( 'SAMLRequest' , encodedRequest ) ;
145116 redirectUrl . searchParams . set ( 'RelayState' , relayStateId ) ;
146117
@@ -167,6 +138,9 @@ export default class SamlController {
167138
168139 /**
169140 * Handle ACS callback (POST /auth/sso/saml/:workspaceId/acs)
141+ * @param req - Express request object
142+ * @param res - Express response object
143+ * @returns void
170144 */
171145 public async handleAcs ( req : express . Request , res : express . Response ) : Promise < void > {
172146 const { workspaceId } = req . params ;
@@ -181,6 +155,7 @@ export default class SamlController {
181155 if ( ! this . isValidWorkspaceId ( workspaceId ) ) {
182156 this . log ( 'warn' , '[ACS] Invalid workspace ID format:' , sgr ( workspaceId , Effect . ForegroundRed ) ) ;
183157 res . status ( 400 ) . json ( { error : 'Invalid workspace ID' } ) ;
158+
184159 return ;
185160 }
186161
@@ -190,6 +165,7 @@ export default class SamlController {
190165 if ( ! samlResponse ) {
191166 this . log ( 'warn' , '[ACS] Missing SAML response for workspace:' , sgr ( workspaceId , Effect . ForegroundCyan ) ) ;
192167 res . status ( 400 ) . json ( { error : 'SAML response is required' } ) ;
168+
193169 return ;
194170 }
195171
@@ -201,6 +177,7 @@ export default class SamlController {
201177 if ( ! workspace || ! workspace . sso ?. enabled ) {
202178 this . log ( 'warn' , '[ACS] SSO not enabled for workspace:' , sgr ( workspaceId , Effect . ForegroundCyan ) ) ;
203179 res . status ( 400 ) . json ( { error : 'SSO is not enabled for this workspace' } ) ;
180+
204181 return ;
205182 }
206183
@@ -249,6 +226,7 @@ export default class SamlController {
249226 sgr ( samlData . inResponseTo . slice ( 0 , 8 ) , Effect . ForegroundGray )
250227 ) ;
251228 res . status ( 400 ) . json ( { error : 'Invalid SAML response: InResponseTo validation failed' } ) ;
229+
252230 return ;
253231 }
254232 }
@@ -261,6 +239,7 @@ export default class SamlController {
261239 sgr ( error instanceof Error ? error . message : 'Unknown error' , Effect . ForegroundRed )
262240 ) ;
263241 res . status ( 400 ) . json ( { error : 'Invalid SAML response' } ) ;
242+
264243 return ;
265244 }
266245
@@ -310,6 +289,7 @@ export default class SamlController {
310289 */
311290 const callbackPath = `/login/sso/${ workspaceId } ` ;
312291 const frontendUrl = new URL ( callbackPath , process . env . GARAGE_URL || 'http://localhost:3000' ) ;
292+
313293 frontendUrl . searchParams . set ( 'access_token' , tokens . accessToken ) ;
314294 frontendUrl . searchParams . set ( 'refresh_token' , tokens . refreshToken ) ;
315295 frontendUrl . searchParams . set ( 'returnUrl' , finalReturnUrl ) ;
@@ -340,6 +320,7 @@ export default class SamlController {
340320 sgr ( error . message , Effect . ForegroundRed )
341321 ) ;
342322 res . status ( 400 ) . json ( { error : 'Invalid SAML response' } ) ;
323+
343324 return ;
344325 }
345326
@@ -354,6 +335,56 @@ export default class SamlController {
354335 }
355336 }
356337
338+ /**
339+ * Log message with SSO prefix
340+ *
341+ * @param level - log level ('log', 'warn', 'error', 'info', 'success')
342+ * @param args - arguments to log
343+ */
344+ private log ( level : 'log' | 'warn' | 'error' | 'info' | 'success' , ...args : unknown [ ] ) : void {
345+ const colors = {
346+ log : Effect . ForegroundGreen ,
347+ warn : Effect . ForegroundYellow ,
348+ error : Effect . ForegroundRed ,
349+ info : Effect . ForegroundBlue ,
350+ success : [ Effect . ForegroundGreen , Effect . Bold ] ,
351+ } ;
352+
353+ let logger : typeof console . log ;
354+
355+ if ( level === 'error' ) {
356+ logger = console . error ;
357+ } else if ( level === 'warn' ) {
358+ logger = console . warn ;
359+ } else {
360+ logger = console . log ;
361+ }
362+
363+ logger ( sgr ( '[SSO]' , colors [ level ] ) , ...args ) ;
364+ }
365+
366+ /**
367+ * Validate workspace ID format
368+ *
369+ * @param workspaceId - workspace ID to validate
370+ * @returns true if valid, false otherwise
371+ */
372+ private isValidWorkspaceId ( workspaceId : string ) : boolean {
373+ return ObjectId . isValid ( workspaceId ) ;
374+ }
375+
376+ /**
377+ * Compose Assertion Consumer Service URL for workspace
378+ *
379+ * @param workspaceId - workspace ID
380+ * @returns ACS URL
381+ */
382+ private getAcsUrl ( workspaceId : string ) : string {
383+ const apiUrl = process . env . API_URL || 'https://api.hawk.so' ;
384+
385+ return `${ apiUrl } /auth/sso/saml/${ workspaceId } /acs` ;
386+ }
387+
357388 /**
358389 * Handle user provisioning (JIT or invite-only)
359390 *
@@ -462,4 +493,3 @@ export default class SamlController {
462493 }
463494 }
464495}
465-
0 commit comments