Skip to content

Commit

Permalink
[server] add vscode(-insiders) ouath2 clients
Browse files Browse the repository at this point in the history
  • Loading branch information
akosyakov authored and roboquat committed Apr 12, 2022
1 parent f1290c8 commit a78f4ef
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 50 deletions.
2 changes: 1 addition & 1 deletion components/gitpod-db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
],
"dependencies": {
"@gitpod/gitpod-protocol": "0.1.5",
"@jmondi/oauth2-server": "^1.1.0",
"@jmondi/oauth2-server": "^2.2.2",
"mysql": "^2.18.1",
"reflect-metadata": "^0.1.13",
"the-big-username-blacklist": "^1.5.2",
Expand Down
4 changes: 2 additions & 2 deletions components/gitpod-db/src/typeorm/auth-code-repository-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
return (await this.getEntityManager()).getRepository<DBOAuthAuthCodeEntry>(DBOAuthAuthCodeEntry);
}

public async getByIdentifier(authCodeCode: string): Promise<OAuthAuthCode> {
public async getByIdentifier(authCodeCode: string): Promise<DBOAuthAuthCodeEntry> {
const authCodeRepo = await this.getOauthAuthCodeRepo();
let authCodes = await authCodeRepo.find({ code: authCodeCode });
authCodes = authCodes.filter((te) => new Date(te.expiresAt).getTime() > Date.now());
Expand All @@ -54,7 +54,7 @@ export class AuthCodeRepositoryDB implements OAuthAuthCodeRepository {
scopes: scopes,
};
}
public async persist(authCode: OAuthAuthCode): Promise<void> {
public async persist(authCode: DBOAuthAuthCodeEntry): Promise<void> {
const authCodeRepo = await this.getOauthAuthCodeRepo();
authCodeRepo.save(authCode);
}
Expand Down
4 changes: 2 additions & 2 deletions components/gitpod-db/src/typeorm/entity/db-oauth-auth-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

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

@Column({
type: "timestamp",
Expand Down
2 changes: 1 addition & 1 deletion components/gitpod-db/src/typeorm/user-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ export class TypeORMUserDBImpl implements UserDB {
} else {
var user: MaybeUser;
if (accessToken.user) {
user = await this.findUserById(accessToken.user.id);
user = await this.findUserById(accessToken.user.id.toString());
}
dbToken = {
tokenHash,
Expand Down
2 changes: 1 addition & 1 deletion components/proxy/conf/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ https://{$GITPOD_DOMAIN} {
@codesync path /code-sync*
handle @codesync {
gitpod.cors_origin {
base_domain {$GITPOD_DOMAIN}
any_domain true
}

import compression
Expand Down
18 changes: 16 additions & 2 deletions components/proxy/plugins/corsorigin/cors_origin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func init() {

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

// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (m CorsOrigin) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
var allowedOrigins []string
if m.AnyDomain {
allowedOrigins = []string{"*"}
} else {
allowedOrigins = []string{"*." + m.BaseDomain}
}
c := cors.New(cors.Options{
AllowedOrigins: []string{"*." + m.BaseDomain},
AllowedOrigins: allowedOrigins,
AllowedMethods: allowedMethods,
AllowedHeaders: allowedHeaders,
ExposedHeaders: exposeHeaders,
Expand Down Expand Up @@ -84,6 +91,13 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}

switch key {
case "any_domain":
b, err := strconv.ParseBool(value)
if err != nil {
return d.Errf("invalid boolean value for subdirective any_domain '%s'", value)
}

m.AnyDomain = b
case "base_domain":
m.BaseDomain = value
case "debug":
Expand All @@ -98,7 +112,7 @@ func (m *CorsOrigin) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}

if m.BaseDomain == "" {
if !m.AnyDomain && m.BaseDomain == "" {
return fmt.Errorf("Please configure the base_domain subdirective")
}

Expand Down
2 changes: 1 addition & 1 deletion components/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@gitpod/ws-manager": "0.1.5",
"@google-cloud/storage": "^5.6.0",
"@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
"@jmondi/oauth2-server": "^1.1.0",
"@jmondi/oauth2-server": "^2.2.2",
"@octokit/rest": "18.6.1",
"@probot/get-private-key": "^1.1.1",
"amqplib": "^0.8.0",
Expand Down
20 changes: 20 additions & 0 deletions components/server/src/oauth-server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,30 @@ const jetBrainsGateway: OAuthClient = {
],
};

function createVSCodeClient(protocol: "vscode" | "vscode-insiders"): OAuthClient {
return {
id: protocol + "-" + "gitpod",
name: `VS Code${protocol === "vscode-insiders" ? " Insiders" : ""}: Gitpod extension`,
redirectUris: [protocol + "://gitpod.gitpod-desktop/complete-gitpod-auth"],
allowedGrants: ["authorization_code"],
scopes: [
{ name: "function:getGitpodTokenScopes" },
{ name: "function:getLoggedInUser" },
{ name: "function:accessCodeSyncStorage" },
{ name: "resource:default" },
],
};
}

const vscode = createVSCodeClient("vscode");
const vscodeInsiders = createVSCodeClient("vscode-insiders");

export const inMemoryDatabase: InMemory = {
clients: {
[localClient.id]: localClient,
[jetBrainsGateway.id]: jetBrainsGateway,
[vscode.id]: vscode,
[vscodeInsiders.id]: vscodeInsiders,
},
tokens: {},
scopes: {},
Expand Down
67 changes: 31 additions & 36 deletions components/server/src/oauth-server/oauth-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { AuthCodeRepositoryDB } from "@gitpod/gitpod-db/lib/typeorm/auth-code-re
import { UserDB } from "@gitpod/gitpod-db/lib/user-db";
import { User } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { OAuthException, OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
import { OAuthRequest, OAuthResponse } from "@jmondi/oauth2-server";
import { handleExpressResponse, handleExpressError } from "@jmondi/oauth2-server/dist/adapters/express";
import * as express from "express";
import { inject, injectable } from "inversify";
import { URL } from "url";
import { Config } from "../config";
import { clientRepository, createAuthorizationServer } from "./oauth-authorization-server";

Expand Down Expand Up @@ -58,10 +60,33 @@ export class OAuthController {
const rt = req.query.redirect_uri?.toString();
if (!rt || !rt.startsWith("http://127.0.0.1:")) {
log.error(`/oauth/authorize: invalid returnTo URL: "${rt}"`);
}

const client = await clientRepository.getByIdentifier(clientID);
if (client) {
if (typeof req.query.redirect_uri !== "string") {
log.error(req.query.redirect_uri ? "Missing redirect URI" : "Invalid format of redirect URI");
res.sendStatus(400);
return false;
}

const normalizedRedirectUri = new URL(req.query.redirect_uri);
normalizedRedirectUri.search = "";

if (!client.redirectUris.some((u) => new URL(u).toString() === normalizedRedirectUri.toString())) {
log.error(`/oauth/authorize: invalid returnTo URL: "${req.query.redirect_uri}"`);
res.sendStatus(400);
return false;
}
} else {
log.error(`/oauth/authorize unknown client id: "${clientID}"`);
res.sendStatus(400);
return false;
}
res.redirect(`${rt}/?approved=no`);

const redirectUri = new URL(req.query.redirect_uri);
redirectUri.searchParams.append("approved", "no");
res.redirect(redirectUri.toString());
return false;
} else if (wasApproved == "yes") {
const additionalData = (user.additionalData = user.additionalData || {});
Expand Down Expand Up @@ -133,53 +158,23 @@ export class OAuthController {

// Return the HTTP redirect response
const oauthResponse = await authorizationServer.completeAuthorizationRequest(authRequest);
return handleResponse(req, res, oauthResponse);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleError(e, res);
handleExpressError(e, res);
}
});

router.post("/oauth/token", async (req: express.Request, res: express.Response) => {
const response = new OAuthResponse(res);
try {
const oauthResponse = await authorizationServer.respondToAccessTokenRequest(req, response);
return handleResponse(req, res, oauthResponse);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleError(e, res);
handleExpressError(e, res);
return;
}
});

function handleError(e: Error | undefined, res: express.Response) {
if (e instanceof OAuthException) {
res.status(e.status);
res.send({
status: e.status,
message: e.message,
stack: e.stack,
});
return;
}
// Generic error
res.status(500);
res.send({
err: e,
});
}

function handleResponse(req: express.Request, res: express.Response, response: OAuthResponse) {
if (response.status === 302) {
if (!response.headers.location) {
throw new Error("missing redirect location");
}
res.set(response.headers);
res.redirect(response.headers.location);
} else {
res.set(response.headers);
res.status(response.status).send(response.body);
}
}

return router;
}
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1727,10 +1727,10 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"

"@jmondi/oauth2-server@^1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-1.1.1.tgz#cb2530c17e2c7db3cc632bb68ec0f38a54c7ae1c"
integrity sha512-my3776n6TDsJQJ+nONG52VNgTQ7veH9lo4kb/AAWt9Rko6VBuMxOb/KxcYdkDrpOznJ036+tVveuhY7zSJjGYg==
"@jmondi/oauth2-server@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-2.2.2.tgz#e99b6edcd068c1a58423e2e8e94eeb0acb048b39"
integrity sha512-U9038EvDQJwc6SUxGjNfP1nhcyIzUuo4MLDBjWEp4ieuoyMYFBlMtmUbXcIEvvlwWQSXneUm7+TcTBcNHKrE8w==
dependencies:
jsonwebtoken "^8.5.1"
ms "^2.1.3"
Expand Down

0 comments on commit a78f4ef

Please sign in to comment.