Skip to content

Commit 1029829

Browse files
committed
[server] add vscode(-insiders) ouath2 clients
1 parent a996c98 commit 1029829

File tree

10 files changed

+90
-63
lines changed

10 files changed

+90
-63
lines changed

components/gitpod-db/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
],
2424
"dependencies": {
2525
"@gitpod/gitpod-protocol": "0.1.5",
26-
"@jmondi/oauth2-server": "^1.1.0",
26+
"@jmondi/oauth2-server": "^2.1.0",
2727
"mysql": "^2.15.0",
2828
"reflect-metadata": "^0.1.10",
2929
"the-big-username-blacklist": "^1.5.2",

components/gitpod-db/src/typeorm/auth-code-repository-db.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
2727
return (await this.getEntityManager()).getRepository<DBOAuthAuthCodeEntry>(DBOAuthAuthCodeEntry);
2828
}
2929

30-
public async getByIdentifier(authCodeCode: string): Promise<OAuthAuthCode> {
30+
public async getByIdentifier(authCodeCode: string): Promise<DBOAuthAuthCodeEntry> {
3131
const authCodeRepo = await this.getOauthAuthCodeRepo();
3232
let authCodes = await authCodeRepo.find({ code: authCodeCode });
3333
authCodes = authCodes.filter(te => (new Date(te.expiresAt)).getTime() > Date.now());
@@ -48,7 +48,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
4848
scopes: scopes,
4949
};
5050
}
51-
public async persist(authCode: OAuthAuthCode): Promise<void> {
51+
public async persist(authCode: DBOAuthAuthCodeEntry): Promise<void> {
5252
const authCodeRepo = await this.getOauthAuthCodeRepo();
5353
authCodeRepo.save(authCode);
5454
}

components/gitpod-db/src/typeorm/entity/db-oauth-auth-code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { OAuthAuthCode, OAuthClient, OAuthScope } from "@jmondi/oauth2-server";
7+
import { CodeChallengeMethod, OAuthAuthCode, OAuthClient, OAuthScope } from "@jmondi/oauth2-server";
88
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
99
import { Transformer } from "../transformer";
1010
import { DBUser } from "./db-user";
@@ -38,7 +38,7 @@ export class DBOAuthAuthCodeEntry implements OAuthAuthCode {
3838
type: "varchar",
3939
length: 10,
4040
})
41-
codeChallengeMethod: string
41+
codeChallengeMethod: CodeChallengeMethod
4242

4343
@Column({
4444
type: 'timestamp',

components/gitpod-db/src/typeorm/user-db-impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ export class TypeORMUserDBImpl implements UserDB {
419419
} else {
420420
var user: MaybeUser;
421421
if (accessToken.user) {
422-
user = await this.findUserById(accessToken.user.id)
422+
user = await this.findUserById(String(accessToken.user.id))
423423
}
424424
dbToken = {
425425
tokenHash,

components/proxy/conf/Caddyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ https://{$GITPOD_DOMAIN} {
210210
@codesync path /code-sync*
211211
handle @codesync {
212212
gitpod.cors_origin {
213-
base_domain {$GITPOD_DOMAIN}
213+
any_domain true
214214
}
215215

216216
import compression

components/proxy/plugins/corsorigin/cors_origin.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func init() {
2727

2828
// CorsOrigin implements an HTTP handler that generates a valid CORS Origin value
2929
type CorsOrigin struct {
30+
AnyDomain bool `json:"any_domain,omitempty"`
3031
BaseDomain string `json:"base_domain,omitempty"`
3132
Debug bool `json:"debug,omitempty"`
3233
}
@@ -50,8 +51,14 @@ var (
5051

5152
// ServeHTTP implements caddyhttp.MiddlewareHandler.
5253
func (m CorsOrigin) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
54+
var allowedOrigins []string
55+
if m.AnyDomain {
56+
allowedOrigins = []string{"*"}
57+
} else {
58+
allowedOrigins = []string{"*." + m.BaseDomain}
59+
}
5360
c := cors.New(cors.Options{
54-
AllowedOrigins: []string{"*." + m.BaseDomain},
61+
AllowedOrigins: allowedOrigins,
5562
AllowedMethods: allowedMethods,
5663
AllowedHeaders: allowedHeaders,
5764
ExposedHeaders: exposeHeaders,
@@ -84,6 +91,13 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
8491
}
8592

8693
switch key {
94+
case "any_domain":
95+
b, err := strconv.ParseBool(value)
96+
if err != nil {
97+
return d.Errf("invalid boolean value for subdirective any_domain '%s'", value)
98+
}
99+
100+
m.AnyDomain = b
87101
case "base_domain":
88102
m.BaseDomain = value
89103
case "debug":
@@ -98,7 +112,7 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
98112
}
99113
}
100114

101-
if m.BaseDomain == "" {
115+
if !m.AnyDomain && m.BaseDomain == "" {
102116
return fmt.Errorf("Please configure the base_domain subdirective")
103117
}
104118

components/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@gitpod/ws-manager": "0.1.5",
4040
"@google-cloud/storage": "^5.6.0",
4141
"@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
42-
"@jmondi/oauth2-server": "^1.1.0",
42+
"@jmondi/oauth2-server": "^2.1.0",
4343
"@octokit/rest": "18.5.6",
4444
"@probot/get-private-key": "1.1.0",
4545
"amqplib": "^0.5.2",

components/server/src/oauth-server/db.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface InMemory {
1717
}
1818

1919
// Scopes
20-
const scopes: OAuthScope[] = [
20+
const localAppScopes: OAuthScope[] = [
2121
{ name: "function:getGitpodTokenScopes" },
2222
{ name: "function:getWorkspace" },
2323
{ name: "function:getWorkspaces" },
@@ -35,16 +35,39 @@ const localClient: OAuthClient = {
3535
// NOTE: these need to be kept in sync with the port range in the local app
3636
redirectUris: Array.from({ length: 10 }, (_, i) => 'http://127.0.0.1:' + (63110 + i)),
3737
allowedGrants: ['authorization_code'],
38-
scopes,
38+
scopes: localAppScopes,
3939
}
4040

41+
function createVSCodeClient(protocol: 'vscode' | 'vscode-insiders'): OAuthClient {
42+
return {
43+
id: protocol + '-' + 'gitpod',
44+
name: `VS Code${protocol === 'vscode-insiders' ? ' Insiders' : ''}: Gitpod extension`,
45+
redirectUris: [protocol + '://gitpod.gitpod-desktop/complete-gitpod-auth'],
46+
allowedGrants: ['authorization_code'],
47+
scopes: [
48+
{ name: "function:getGitpodTokenScopes" },
49+
{ name: "function:getLoggedInUser" },
50+
{ name: "function:accessCodeSyncStorage" },
51+
{ name: "resource:default" }
52+
],
53+
}
54+
}
55+
56+
const vscode = createVSCodeClient('vscode');
57+
const vscodeInsiders = createVSCodeClient('vscode-insiders');
58+
4159
export const inMemoryDatabase: InMemory = {
4260
clients: {
4361
[localClient.id]: localClient,
62+
[vscode.id]: vscode,
63+
[vscodeInsiders.id]: vscodeInsiders
4464
},
4565
tokens: {},
4666
scopes: {},
4767
};
48-
for (const scope of scopes) {
49-
inMemoryDatabase.scopes[scope.name] = scope;
68+
for (const clientId in inMemoryDatabase.clients) {
69+
const client = inMemoryDatabase.clients[clientId];
70+
for (const scope of client.scopes) {
71+
inMemoryDatabase.scopes[scope.name] = scope;
72+
}
5073
}

components/server/src/oauth-server/oauth-controller.ts

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import { AuthCodeRepositoryDB } from '@gitpod/gitpod-db/lib/typeorm/auth-code-re
88
import { UserDB } from '@gitpod/gitpod-db/lib/user-db';
99
import { User } from "@gitpod/gitpod-protocol";
1010
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
11-
import { OAuthException, OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
11+
import { OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
12+
import { handleExpressResponse, handleExpressError } from "@jmondi/oauth2-server/dist/adapters/express"
1213
import * as express from 'express';
1314
import { inject, injectable } from "inversify";
15+
import { URL } from 'url';
1416
import { Config } from '../config';
1517
import { clientRepository, createAuthorizationServer } from './oauth-authorization-server';
1618

@@ -40,7 +42,7 @@ export class OAuthController {
4042
}
4143

4244
private async hasApproval(user: User, clientID: string, req: express.Request, res: express.Response): Promise<boolean> {
43-
// Have they just authorized, or not, the local-app?
45+
// Have they just authorized, or not, registered clients?
4446
const wasApproved = req.query['approved'] || '';
4547
if (wasApproved === 'no') {
4648
const additionalData = user?.additionalData;
@@ -49,14 +51,25 @@ export class OAuthController {
4951
await this.userDb.updateUserPartial(user);
5052
}
5153

52-
// Let the local app know they rejected the approval
53-
const rt = req.query.redirect_uri;
54-
if (!rt || !rt.startsWith("http://127.0.0.1:")) {
55-
log.error(`/oauth/authorize: invalid returnTo URL: "${rt}"`)
54+
// Let the client know they rejected the approval
55+
const client = await clientRepository.getByIdentifier(clientID);
56+
if (client) {
57+
const normalizedRedirectUri = new URL(req.query.redirect_uri);
58+
normalizedRedirectUri.search = '';
59+
60+
if (!client.redirectUris.some(u => new URL(u).toString() === normalizedRedirectUri.toString())) {
61+
log.error(`/oauth/authorize: invalid returnTo URL: "${req.query.redirect_uri}"`)
62+
res.sendStatus(400);
63+
return false;
64+
}
65+
} else {
66+
log.error(`/oauth/authorize unknown client id: "${clientID}"`)
5667
res.sendStatus(400);
5768
return false;
5869
}
59-
res.redirect(`${rt}/?approved=no`);
70+
const redirectUri = new URL(req.query.redirect_uri);
71+
redirectUri.searchParams.append('approved', 'no');
72+
res.redirect(redirectUri.toString());
6073
return false;
6174
} else if (wasApproved == 'yes') {
6275
const additionalData = user.additionalData = user.additionalData || {};
@@ -123,53 +136,23 @@ export class OAuthController {
123136

124137
// Return the HTTP redirect response
125138
const oauthResponse = await authorizationServer.completeAuthorizationRequest(authRequest);
126-
return handleResponse(req, res, oauthResponse);
139+
return handleExpressResponse(res, oauthResponse);
127140
} catch (e) {
128-
handleError(e, res);
141+
handleExpressError(e, res);
129142
}
130143
});
131144

132145
router.post("/oauth/token", async (req: express.Request, res: express.Response) => {
133146
const response = new OAuthResponse(res);
134147
try {
135148
const oauthResponse = await authorizationServer.respondToAccessTokenRequest(req, response);
136-
return handleResponse(req, res, oauthResponse);
149+
return handleExpressResponse(res, oauthResponse);
137150
} catch (e) {
138-
handleError(e, res);
151+
handleExpressError(e, res);
139152
return;
140153
}
141154
});
142155

143-
function handleError(e: Error | undefined, res: express.Response) {
144-
if (e instanceof OAuthException) {
145-
res.status(e.status);
146-
res.send({
147-
status: e.status,
148-
message: e.message,
149-
stack: e.stack,
150-
});
151-
return;
152-
}
153-
// Generic error
154-
res.status(500)
155-
res.send({
156-
err: e
157-
})
158-
}
159-
160-
function handleResponse(req: express.Request, res: express.Response, response: OAuthResponse) {
161-
if (response.status === 302) {
162-
if (!response.headers.location) {
163-
throw new Error("missing redirect location");
164-
}
165-
res.set(response.headers);
166-
res.redirect(response.headers.location);
167-
} else {
168-
res.set(response.headers);
169-
res.status(response.status).send(response.body);
170-
}
171-
}
172-
173156
return router;
174157
}
175158
}

yarn.lock

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3286,10 +3286,10 @@
32863286
"@types/yargs" "^15.0.0"
32873287
chalk "^4.0.0"
32883288

3289-
"@jmondi/oauth2-server@^1.1.0":
3290-
version "1.1.0"
3291-
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-1.1.0.tgz#37014c4aceaee9b4559df224a4d3a743e66e5929"
3292-
integrity sha512-7UuliIJVnn3ISVEQ/CeRdKdY6gQb+RbOAlCZysdWytcvWNF+5Xb32ARbjOnzzLRzlh5ZxAKC5Zta0TZSKeXchg==
3289+
"@jmondi/oauth2-server@^2.1.0":
3290+
version "2.1.0"
3291+
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-2.1.0.tgz#ffa10dd8b9c5c8b480824bf0ecf104d5d00ec4a7"
3292+
integrity sha512-R6zxiKCC0MyAk3M9rV8gM0bDqvXNZgiDLTriefkfsZIXZoVw52W2X8usf5Y8qSwnmdP4u3ijXbb2fSUAcSbDdA==
32933293
dependencies:
32943294
jsonwebtoken "^8.5.1"
32953295
ms "^2.1.3"
@@ -4675,7 +4675,7 @@
46754675
"@types/history" "*"
46764676
"@types/react" "*"
46774677

4678-
"@types/react@*", "@types/react@^17.0.0":
4678+
"@types/react@*", "@types/react@17.0.0", "@types/react@^17.0.0":
46794679
version "17.0.0"
46804680
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
46814681
integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==
@@ -14874,7 +14874,7 @@ mz@^2.4.0:
1487414874
object-assign "^4.0.1"
1487514875
thenify-all "^1.0.0"
1487614876

14877-
nan@^2.12.1, nan@^2.13.2, nan@^2.9.2:
14877+
nan@2.14.1, nan@^2.12.1, nan@^2.13.2, nan@^2.14.0, nan@^2.9.2:
1487814878
version "2.14.1"
1487914879
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
1488014880
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
@@ -15421,6 +15421,13 @@ onetime@^5.1.2:
1542115421
dependencies:
1542215422
mimic-fn "^2.1.0"
1542315423

15424+
15425+
version "7.2.1"
15426+
resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.1.tgz#51775834f7819b6e31aa878706aa7f65ad16b07f"
15427+
integrity sha512-WPS/e1uzhswPtJSe+Zls/kAj27+lEqZjCmRSjnYk/Z4L2Mu+lJC2JWtkZhPJe4kZeTQfz7ClcLyXlI4J68MG2w==
15428+
dependencies:
15429+
nan "^2.14.0"
15430+
1542415431
open@^7.0.2:
1542515432
version "7.4.2"
1542615433
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
@@ -17555,7 +17562,7 @@ react-dev-utils@^11.0.3:
1755517562
strip-ansi "6.0.0"
1755617563
text-table "0.2.0"
1755717564

17558-
react-dom@^17.0.1:
17565+
react-dom@17.0.1, react-dom@^17.0.1:
1755917566
version "17.0.1"
1756017567
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
1756117568
integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
@@ -17684,7 +17691,7 @@ react-scripts@^4.0.3:
1768417691
optionalDependencies:
1768517692
fsevents "^2.1.3"
1768617693

17687-
react@^17.0.1:
17694+
react@17.0.1, react@^17.0.1:
1768817695
version "17.0.1"
1768917696
resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
1769017697
integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==

0 commit comments

Comments
 (0)