-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refacto(auth): improve type + remove complexity #9949
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,24 +38,14 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') { | |
}); | ||
} | ||
|
||
authenticate(req: any, options: any) { | ||
authenticate(req: Request, options: any) { | ||
options = { | ||
...options, | ||
state: JSON.stringify({ | ||
workspaceInviteHash: req.params.workspaceInviteHash, | ||
workspaceInviteHash: req.query.workspaceInviteHash, | ||
workspaceId: req.params.workspaceId, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: workspaceId is still being read from req.params while other parameters were moved to req.query. This inconsistency could cause issues with workspace ID retrieval. |
||
...(req.params.billingCheckoutSessionState | ||
? { | ||
billingCheckoutSessionState: | ||
req.params.billingCheckoutSessionState, | ||
} | ||
: {}), | ||
...(req.params.workspacePersonalInviteToken | ||
? { | ||
workspacePersonalInviteToken: | ||
req.params.workspacePersonalInviteToken, | ||
} | ||
: {}), | ||
billingCheckoutSessionState: req.query.billingCheckoutSessionState, | ||
workspacePersonalInviteToken: req.query.workspacePersonalInviteToken, | ||
}), | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,11 +3,26 @@ | |||||||||||||
import { Injectable } from '@nestjs/common'; | ||||||||||||||
import { PassportStrategy } from '@nestjs/passport'; | ||||||||||||||
|
||||||||||||||
import { isEmail } from 'class-validator'; | ||||||||||||||
import { Request } from 'express'; | ||||||||||||||
import { Strategy, StrategyOptions, TokenSet } from 'openid-client'; | ||||||||||||||
|
||||||||||||||
import { | ||||||||||||||
Strategy, | ||||||||||||||
StrategyOptions, | ||||||||||||||
StrategyVerifyCallbackReq, | ||||||||||||||
} from 'openid-client'; | ||||||||||||||
AuthException, | ||||||||||||||
AuthExceptionCode, | ||||||||||||||
} from 'src/engine/core-modules/auth/auth.exception'; | ||||||||||||||
|
||||||||||||||
export type OIDCRequest = Omit< | ||||||||||||||
Request, | ||||||||||||||
'user' | 'workspace' | 'workspaceMetadataVersion' | ||||||||||||||
> & { | ||||||||||||||
user: { | ||||||||||||||
identityProviderId: string; | ||||||||||||||
email: string; | ||||||||||||||
firstName?: string | null; | ||||||||||||||
lastName?: string | null; | ||||||||||||||
}; | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
@Injectable() | ||||||||||||||
export class OIDCAuthStrategy extends PassportStrategy( | ||||||||||||||
|
@@ -30,7 +45,7 @@ export class OIDCAuthStrategy extends PassportStrategy( | |||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async authenticate(req: any, options: any) { | ||||||||||||||
async authenticate(req: Request, options: any) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: options parameter should be properly typed instead of 'any' |
||||||||||||||
return super.authenticate(req, { | ||||||||||||||
...options, | ||||||||||||||
state: JSON.stringify({ | ||||||||||||||
|
@@ -39,37 +54,50 @@ export class OIDCAuthStrategy extends PassportStrategy( | |||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
validate: StrategyVerifyCallbackReq<{ | ||||||||||||||
private extractState(req: Request): { | ||||||||||||||
identityProviderId: string; | ||||||||||||||
user: { | ||||||||||||||
email?: string; | ||||||||||||||
firstName?: string | null; | ||||||||||||||
lastName?: string | null; | ||||||||||||||
}; | ||||||||||||||
}> = async (req, tokenset, done) => { | ||||||||||||||
} { | ||||||||||||||
try { | ||||||||||||||
const state = JSON.parse( | ||||||||||||||
'query' in req && | ||||||||||||||
req.query && | ||||||||||||||
typeof req.query === 'object' && | ||||||||||||||
'state' in req.query && | ||||||||||||||
req.query.state && | ||||||||||||||
typeof req.query.state === 'string' | ||||||||||||||
req.query.state && typeof req.query.state === 'string' | ||||||||||||||
? req.query.state | ||||||||||||||
: '{}', | ||||||||||||||
); | ||||||||||||||
|
||||||||||||||
if (!state.identityProviderId) { | ||||||||||||||
throw new Error(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return { | ||||||||||||||
identityProviderId: state.identityProviderId, | ||||||||||||||
}; | ||||||||||||||
} catch (err) { | ||||||||||||||
throw new AuthException('Invalid state', AuthExceptionCode.INVALID_INPUT); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async validate( | ||||||||||||||
req: Request, | ||||||||||||||
tokenset: TokenSet, | ||||||||||||||
done: (err: any, user?: OIDCRequest['user']) => void, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: err parameter should be properly typed instead of 'any' |
||||||||||||||
) { | ||||||||||||||
try { | ||||||||||||||
const state = this.extractState(req); | ||||||||||||||
|
||||||||||||||
const userinfo = await this.client.userinfo(tokenset); | ||||||||||||||
|
||||||||||||||
const user = { | ||||||||||||||
if (!userinfo.email || !isEmail(userinfo.email)) { | ||||||||||||||
return done(new Error('Invalid email')); | ||||||||||||||
} | ||||||||||||||
Comment on lines
+89
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: error message should use AuthException with proper error code for consistency with extractState method
Suggested change
|
||||||||||||||
|
||||||||||||||
done(null, { | ||||||||||||||
email: userinfo.email, | ||||||||||||||
identityProviderId: state.identityProviderId, | ||||||||||||||
...(userinfo.given_name ? { firstName: userinfo.given_name } : {}), | ||||||||||||||
...(userinfo.family_name ? { lastName: userinfo.family_name } : {}), | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
done(null, { user, identityProviderId: state.identityProviderId }); | ||||||||||||||
}); | ||||||||||||||
} catch (err) { | ||||||||||||||
done(err); | ||||||||||||||
} | ||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: The payload type is too restrictive - OIDC and SAML responses typically include firstName and lastName which are used in formatUserDataPayload()