Skip to content

Commit c384e71

Browse files
committed
add logs to the sso controller
1 parent 154f59e commit c384e71

File tree

1 file changed

+190
-47
lines changed

1 file changed

+190
-47
lines changed

src/sso/saml/controller.ts

Lines changed: 190 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ContextFactories } from '../../types/graphql';
77
import { SamlResponseData } from '../types';
88
import WorkspaceModel from '../../models/workspace';
99
import UserModel from '../../models/user';
10+
import { sgr, Effect } from '../../utils/ansi';
1011

1112
/**
1213
* Controller for SAML SSO endpoints
@@ -27,6 +28,26 @@ export default class SamlController {
2728
this.factories = factories;
2829
}
2930

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+
3051
/**
3152
* Validate workspace ID format
3253
*
@@ -52,14 +73,16 @@ export default class SamlController {
5273
* Initiate SSO login (GET /auth/sso/saml/:workspaceId)
5374
*/
5475
public async initiateLogin(req: express.Request, res: express.Response): Promise<void> {
76+
const { workspaceId } = req.params;
77+
5578
try {
56-
const { workspaceId } = req.params;
5779
const returnUrl = (req.query.returnUrl as string) || `/workspace/${workspaceId}`;
5880

5981
/**
6082
* Validate workspace ID format
6183
*/
6284
if (!this.isValidWorkspaceId(workspaceId)) {
85+
this.log('warn', 'Invalid workspace ID format:', sgr(workspaceId, Effect.ForegroundRed));
6386
res.status(400).json({ error: 'Invalid workspace ID' });
6487
return;
6588
}
@@ -70,6 +93,7 @@ export default class SamlController {
7093
const workspace = await this.factories.workspacesFactory.findById(workspaceId);
7194

7295
if (!workspace || !workspace.sso?.enabled) {
96+
this.log('warn', 'SSO not enabled for workspace:', sgr(workspaceId, Effect.ForegroundCyan));
7397
res.status(400).json({ error: 'SSO is not enabled for this workspace' });
7498
return;
7599
}
@@ -107,12 +131,23 @@ export default class SamlController {
107131
redirectUrl.searchParams.set('SAMLRequest', encodedRequest);
108132
redirectUrl.searchParams.set('RelayState', relayStateId);
109133

134+
this.log(
135+
'log',
136+
'Initiating SSO login for workspace:',
137+
sgr(workspaceId, [Effect.ForegroundCyan, Effect.Bold]),
138+
'| Request ID:',
139+
sgr(requestId.slice(0, 8), Effect.ForegroundGray)
140+
);
141+
110142
res.redirect(redirectUrl.toString());
111143
} catch (error) {
112-
console.error('SSO initiation error:', {
113-
workspaceId: req.params.workspaceId,
114-
error: error instanceof Error ? error.message : 'Unknown error',
115-
});
144+
this.log(
145+
'error',
146+
'SSO initiation error for workspace:',
147+
sgr(workspaceId, Effect.ForegroundCyan),
148+
'|',
149+
sgr(error instanceof Error ? error.message : 'Unknown error', Effect.ForegroundRed)
150+
);
116151
res.status(500).json({ error: 'Failed to initiate SSO login' });
117152
}
118153
}
@@ -121,15 +156,17 @@ export default class SamlController {
121156
* Handle ACS callback (POST /auth/sso/saml/:workspaceId/acs)
122157
*/
123158
public async handleAcs(req: express.Request, res: express.Response): Promise<void> {
159+
const { workspaceId } = req.params;
160+
124161
try {
125-
const { workspaceId } = req.params;
126162
const samlResponse = req.body.SAMLResponse as string;
127163
const relayStateId = req.body.RelayState as string;
128164

129165
/**
130166
* Validate workspace ID format
131167
*/
132168
if (!this.isValidWorkspaceId(workspaceId)) {
169+
this.log('warn', '[ACS] Invalid workspace ID format:', sgr(workspaceId, Effect.ForegroundRed));
133170
res.status(400).json({ error: 'Invalid workspace ID' });
134171
return;
135172
}
@@ -138,6 +175,7 @@ export default class SamlController {
138175
* Validate required SAML response
139176
*/
140177
if (!samlResponse) {
178+
this.log('warn', '[ACS] Missing SAML response for workspace:', sgr(workspaceId, Effect.ForegroundCyan));
141179
res.status(400).json({ error: 'SAML response is required' });
142180
return;
143181
}
@@ -148,6 +186,7 @@ export default class SamlController {
148186
const workspace = await this.factories.workspacesFactory.findById(workspaceId);
149187

150188
if (!workspace || !workspace.sso?.enabled) {
189+
this.log('warn', '[ACS] SSO not enabled for workspace:', sgr(workspaceId, Effect.ForegroundCyan));
151190
res.status(400).json({ error: 'SSO is not enabled for this workspace' });
152191
return;
153192
}
@@ -171,6 +210,14 @@ export default class SamlController {
171210
workspace.sso.saml
172211
);
173212

213+
this.log(
214+
'log',
215+
'[ACS] SAML response validated for workspace:',
216+
sgr(workspaceId, Effect.ForegroundCyan),
217+
'| User:',
218+
sgr(samlData.email, [Effect.ForegroundMagenta, Effect.Bold])
219+
);
220+
174221
/**
175222
* Validate InResponseTo against stored AuthnRequest
176223
*/
@@ -181,15 +228,25 @@ export default class SamlController {
181228
);
182229

183230
if (!isValidRequest) {
231+
this.log(
232+
'error',
233+
'[ACS] InResponseTo validation failed for workspace:',
234+
sgr(workspaceId, Effect.ForegroundCyan),
235+
'| Request ID:',
236+
sgr(samlData.inResponseTo.slice(0, 8), Effect.ForegroundGray)
237+
);
184238
res.status(400).json({ error: 'Invalid SAML response: InResponseTo validation failed' });
185239
return;
186240
}
187241
}
188242
} catch (error) {
189-
console.error('SAML validation error:', {
190-
workspaceId,
191-
error: error instanceof Error ? error.message : 'Unknown error',
192-
});
243+
this.log(
244+
'error',
245+
'[ACS] SAML validation error for workspace:',
246+
sgr(workspaceId, Effect.ForegroundCyan),
247+
'|',
248+
sgr(error instanceof Error ? error.message : 'Unknown error', Effect.ForegroundRed)
249+
);
193250
res.status(400).json({ error: 'Invalid SAML response' });
194251
return;
195252
}
@@ -203,7 +260,22 @@ export default class SamlController {
203260
/**
204261
* JIT provisioning or invite-only policy
205262
*/
263+
this.log(
264+
'info',
265+
'[ACS] User not found, starting provisioning:',
266+
sgr(samlData.email, Effect.ForegroundMagenta),
267+
'| Workspace:',
268+
sgr(workspaceId, Effect.ForegroundCyan)
269+
);
206270
user = await this.handleUserProvisioning(workspaceId, samlData, workspace);
271+
} else {
272+
this.log(
273+
'log',
274+
'[ACS] Existing user found:',
275+
sgr(samlData.email, Effect.ForegroundMagenta),
276+
'| User ID:',
277+
sgr(user._id.toString().slice(0, 8), Effect.ForegroundGray)
278+
);
207279
}
208280

209281
/**
@@ -225,24 +297,40 @@ export default class SamlController {
225297
frontendUrl.searchParams.set('access_token', tokens.accessToken);
226298
frontendUrl.searchParams.set('refresh_token', tokens.refreshToken);
227299

300+
this.log(
301+
'success',
302+
'[ACS] ✓ SSO login successful:',
303+
sgr(samlData.email, [Effect.ForegroundMagenta, Effect.Bold]),
304+
'| Workspace:',
305+
sgr(workspaceId, Effect.ForegroundCyan),
306+
'| Redirecting to:',
307+
sgr(finalReturnUrl, Effect.ForegroundGray)
308+
);
309+
228310
res.redirect(frontendUrl.toString());
229311
} catch (error) {
230312
/**
231313
* Handle specific error types
232314
*/
233315
if (error instanceof Error && error.message.includes('SAML')) {
234-
console.error('SAML processing error:', {
235-
workspaceId: req.params.workspaceId,
236-
error: error.message,
237-
});
316+
this.log(
317+
'error',
318+
'[ACS] SAML processing error for workspace:',
319+
sgr(workspaceId, Effect.ForegroundCyan),
320+
'|',
321+
sgr(error.message, Effect.ForegroundRed)
322+
);
238323
res.status(400).json({ error: 'Invalid SAML response' });
239324
return;
240325
}
241326

242-
console.error('ACS callback error:', {
243-
workspaceId: req.params.workspaceId,
244-
error: error instanceof Error ? error.message : 'Unknown error',
245-
});
327+
this.log(
328+
'error',
329+
'[ACS] ACS callback error for workspace:',
330+
sgr(workspaceId, Effect.ForegroundCyan),
331+
'|',
332+
sgr(error instanceof Error ? error.message : 'Unknown error', Effect.ForegroundRed)
333+
);
246334
res.status(500).json({ error: 'Failed to process SSO callback' });
247335
}
248336
}
@@ -260,44 +348,99 @@ export default class SamlController {
260348
samlData: SamlResponseData,
261349
workspace: WorkspaceModel
262350
): Promise<UserModel> {
263-
/**
264-
* Find user by email
265-
*/
266-
let user = await this.factories.usersFactory.findByEmail(samlData.email);
267-
268-
if (!user) {
351+
try {
269352
/**
270-
* Create new user (JIT provisioning)
271-
* Password is not set - only SSO login is allowed
353+
* Find user by email
272354
*/
273-
user = await this.factories.usersFactory.create(samlData.email, undefined, undefined);
274-
}
355+
let user = await this.factories.usersFactory.findByEmail(samlData.email);
275356

276-
/**
277-
* Link SAML identity to user
278-
*/
279-
await user.linkSamlIdentity(workspaceId, samlData.nameId, samlData.email);
280-
281-
/**
282-
* Check if user is a member of the workspace
283-
*/
284-
const member = await workspace.getMemberInfo(user._id.toString());
357+
if (!user) {
358+
/**
359+
* Create new user (JIT provisioning)
360+
* Password is not set - only SSO login is allowed
361+
*/
362+
this.log(
363+
'info',
364+
'[Provisioning] Creating new user:',
365+
sgr(samlData.email, [Effect.ForegroundMagenta, Effect.Bold]),
366+
'| Workspace:',
367+
sgr(workspaceId, Effect.ForegroundCyan)
368+
);
369+
user = await this.factories.usersFactory.create(samlData.email, undefined, undefined);
370+
}
285371

286-
if (!member) {
287372
/**
288-
* Add user to workspace (JIT provisioning)
373+
* Link SAML identity to user
289374
*/
290-
await workspace.addMember(user._id.toString());
291-
await user.addWorkspace(workspaceId);
292-
} else if (WorkspaceModel.isPendingMember(member)) {
375+
this.log(
376+
'info',
377+
'[Provisioning] Linking SAML identity for user:',
378+
sgr(samlData.email, Effect.ForegroundMagenta),
379+
'| NameID:',
380+
sgr(samlData.nameId.slice(0, 16) + '...', Effect.ForegroundGray)
381+
);
382+
await user.linkSamlIdentity(workspaceId, samlData.nameId, samlData.email);
383+
293384
/**
294-
* Confirm pending membership
385+
* Check if user is a member of the workspace
295386
*/
296-
await workspace.confirmMembership(user);
297-
await user.confirmMembership(workspaceId);
298-
}
387+
const member = await workspace.getMemberInfo(user._id.toString());
299388

300-
return user;
389+
if (!member) {
390+
/**
391+
* Add user to workspace (JIT provisioning)
392+
*/
393+
this.log(
394+
'log',
395+
'[Provisioning] Adding user to workspace:',
396+
sgr(samlData.email, Effect.ForegroundMagenta),
397+
'| Workspace:',
398+
sgr(workspaceId, Effect.ForegroundCyan)
399+
);
400+
await workspace.addMember(user._id.toString());
401+
await user.addWorkspace(workspaceId);
402+
} else if (WorkspaceModel.isPendingMember(member)) {
403+
/**
404+
* Confirm pending membership
405+
*/
406+
this.log(
407+
'log',
408+
'[Provisioning] Confirming pending membership:',
409+
sgr(samlData.email, Effect.ForegroundMagenta),
410+
'| Workspace:',
411+
sgr(workspaceId, Effect.ForegroundCyan)
412+
);
413+
await workspace.confirmMembership(user);
414+
await user.confirmMembership(workspaceId);
415+
} else {
416+
this.log(
417+
'log',
418+
'[Provisioning] User already member of workspace:',
419+
sgr(samlData.email, Effect.ForegroundMagenta)
420+
);
421+
}
422+
423+
this.log(
424+
'success',
425+
'[Provisioning] ✓ User provisioning completed:',
426+
sgr(samlData.email, [Effect.ForegroundMagenta, Effect.Bold]),
427+
'| User ID:',
428+
sgr(user._id.toString(), Effect.ForegroundGray)
429+
);
430+
431+
return user;
432+
} catch (error) {
433+
this.log(
434+
'error',
435+
'[Provisioning] Provisioning error for user:',
436+
sgr(samlData.email, Effect.ForegroundMagenta),
437+
'| Workspace:',
438+
sgr(workspaceId, Effect.ForegroundCyan),
439+
'|',
440+
sgr(error instanceof Error ? error.message : 'Unknown error', Effect.ForegroundRed)
441+
);
442+
throw error;
443+
}
301444
}
302445
}
303446

0 commit comments

Comments
 (0)