-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Add full OAuth2 server for gitpod local control to use #4164
Conversation
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.9 |
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.10 |
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.21 |
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.32 |
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.33 |
/werft run 👍 started the job as gitpod-build-rl-gplctl-oauth2-server.37 |
a39a280
to
9ffc0b6
Compare
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.
ping
4621c07
to
3119a38
Compare
640390d
to
c67755e
Compare
… created 2 entries.
user, | ||
client, | ||
redirectUri: "", | ||
codeChallenge: undefined, |
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.
I thought the plan was to use PKCE, no? This looks like it's disabled.
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.
It is a somewhat odd approach, but the oauth lib populates it, along with the redirectUri, scopes, etc as part of its flow
That said, I've added a comment, removed the confusing params and added code in the oauth server constructor to make the PKCE-side more explicit:
const authorizationServer = new GitpodAuthorizationServer(
authCodeRepository,
clientRepository,
tokenRepository,
scopeRepository,
userRepository,
jwtService,
{
// Be explicit, communicate intent. Default is true but let's not assume that
requiresPKCE: true,
}
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.
nice!
} | ||
}); | ||
} | ||
public issueAuthCode(client: OAuthClient, user: OAuthUser | undefined, scopes: OAuthScope[]): OAuthAuthCode { |
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.
We used to separate services and persistence. If applied, methods like issueAuthCode
would go to a service module which in turn uses the repository implementation.
authCodes = authCodes.filter(te => (new Date(te.expiresAt)).getTime() > Date.now()); | ||
log.info(`getByIdentifier post: ${JSON.stringify(authCodes)}`); | ||
const authCode = authCodes.length > 0 ? authCodes[0] : undefined; | ||
return new Promise<OAuthAuthCode>((resolve, reject) => { |
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.
in async functions you don't need to return a promise, instead:
if (!authCode) {
throw new Error(...);
}
return authCode;
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.
Right, that was cruft - removed.
const authCodeRepo = await this.getOauthAuthCodeRepo(); | ||
authCodeRepo.save(authCode); | ||
} | ||
public async isRevoked(authCodeCode: string): Promise<boolean> { |
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.
looks like unused. please remove if true.
also the implementation looks more like isExpired
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.
Def called by lib and is an important part of the required interface.
Agreed that it looks like isExpired because that is exactly what it is doing :-)
Given we are very unlikely to want to support long-lived auth tokens I didn't want to go to the trouble of extending the interface to add something more suitable. The intention is to add a cleanup phase to remove expired auth codes at a later date.
if (authCode) { | ||
log.info(`revoke auth ${authCodeCode} ${JSON.stringify(authCode)}`); | ||
// Set date to earliest timestamp that MySQL allows | ||
authCode.expiresAt = new Date(1000); |
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.
why expiring the authCode?
- auth codes are short-living anyways
- that's not used
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.
why expiring the authCode?
See answer above.
- auth codes are short-living anyways
Best practice - yes, but not always and we are using a general purpose lib so it allows for that more unsafe option.
- that's not used
See answer above.
dbToken = { | ||
tokenHash, | ||
name: `local-app`, | ||
type: GitpodTokenType.MACHINE_AUTH_TOKEN, |
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.
@csweichel, what's the meaning of MACHINE_AUTH_TOKEN
? this sounds like a new type.
log.info(`persist access token ${JSON.stringify(accessToken)}`); | ||
var scopes: string[] = []; | ||
for (const scope of accessToken.scopes) { | ||
scopes = scopes.concat(scope.name); |
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.
nit: shorter: const scopes = [...accessToken.scopes]
;
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.
Thanks... that fails with a type mismatch further down, but it is now the more concise:
const scopes = accessToken.scopes.map((s) => s.name);
} | ||
|
||
// Have they authorized the local-app? | ||
const wasApproved = req.query['approved'] || ''; |
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.
how is the approved
param related to the authorization request? is that a shortcut for the interactive part, i.e. the web flow?
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.
Not so much a shortcut, but yes it is part of the web flow, in particular the rejection side so it returns immediately.
It is probably better to keep the explicit rejection in the db as opposed to deleting the entry. That way we can differentiate between not having asked for approval and an explicit rejection. The downside is that if they've changed their mind after initially rejecting it we have no easy way for them to do that in the UI... yet. It can be done via an explicitly formed URL but is not user-friendly.
res.redirect(redirectTo) | ||
return; | ||
} else { | ||
log.error(`/oauth/authorize unknown client id: "${clientID}"`) |
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.
nit: looks like unreachable
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.
Yes, using the hard-coded client id.
It should, and now does, use the requested client id which can fail.
}); | ||
|
||
router.post("/oauth/token", async (req: express.Request, res: express.Response) => { | ||
log.info(`TOKEN: ${JSON.stringify(req.body)}`); |
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.
nit: it's the code at this point
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.
Not sure I understand the comment, can you elaborate pls?
@AlexTugarev - I just realised you had reviewed the old, many-commits-for-testing PR :-( The comments still apply to the new PR as it was a merge-squash to make it a little cleaner. |
No description provided.