From 8de1006b128ceb4ed02832cfef8d53ed8cdafc6e Mon Sep 17 00:00:00 2001 From: rl-gitpod Date: Fri, 21 May 2021 10:24:54 +0000 Subject: [PATCH] [server] add OAuth2 server endpoints (behind a feature flag) to manage client application access to users Gitpod workspaces --- .werft/values.dev.yaml | 1 + chart/templates/server-deployment.yaml | 6 + .../dashboard/public/complete-auth/index.html | 7 +- components/dashboard/src/App.tsx | 8 + components/dashboard/src/Login.tsx | 32 ++- .../dashboard/src/OauthClientApproval.tsx | 48 ++++ components/dashboard/src/provider-utils.tsx | 20 +- components/gitpod-db/README.md | 17 ++ components/gitpod-db/package.json | 7 +- components/gitpod-db/src/container-module.ts | 3 + .../src/typeorm/auth-code-repository-db.ts | 67 ++++++ .../src/typeorm/entity/db-oauth-auth-code.ts | 64 ++++++ .../1620865190518-OAuthAuthCodeDB.ts | 22 ++ .../gitpod-db/src/typeorm/user-db-impl.ts | 98 ++++++++- components/gitpod-db/src/user-db.ts | 9 +- components/gitpod-protocol/src/protocol.ts | 3 + components/local-app/README.md | 15 ++ components/local-app/go.mod | 4 +- components/local-app/go.sum | 6 + components/local-app/main.go | 7 + components/local-app/oauth.go | 208 ++++++++++++++++++ components/server/package.json | 1 + components/server/src/container-module.ts | 3 + components/server/src/env.ts | 2 + components/server/src/oauth-server/db.ts | 49 +++++ .../oauth-authorization-server.ts | 32 +++ .../src/oauth-server/oauth-controller.ts | 175 +++++++++++++++ .../server/src/oauth-server/repository.ts | 47 ++++ components/server/src/server.ts | 3 + components/server/src/user/user-controller.ts | 9 +- gitpod-ws.code-workspace | 1 + yarn.lock | 18 +- 32 files changed, 957 insertions(+), 35 deletions(-) create mode 100644 components/dashboard/src/OauthClientApproval.tsx create mode 100644 components/gitpod-db/README.md create mode 100644 components/gitpod-db/src/typeorm/auth-code-repository-db.ts create mode 100644 components/gitpod-db/src/typeorm/entity/db-oauth-auth-code.ts create mode 100644 components/gitpod-db/src/typeorm/migration/1620865190518-OAuthAuthCodeDB.ts create mode 100644 components/local-app/oauth.go create mode 100644 components/server/src/oauth-server/db.ts create mode 100644 components/server/src/oauth-server/oauth-authorization-server.ts create mode 100644 components/server/src/oauth-server/oauth-controller.ts create mode 100644 components/server/src/oauth-server/repository.ts diff --git a/.werft/values.dev.yaml b/.werft/values.dev.yaml index 5ddf101e144552..49a1012342e05d 100644 --- a/.werft/values.dev.yaml +++ b/.werft/values.dev.yaml @@ -31,6 +31,7 @@ components: makeNewUsersAdmin: true # for development theiaPluginsBucketName: gitpod-core-dev-plugins enableLocalApp: true + enableOAuthServer: true blockNewUsers: true blockNewUsersPasslist: - "gitpod.io" diff --git a/chart/templates/server-deployment.yaml b/chart/templates/server-deployment.yaml index 9761f9d4d7827c..0a6795bd84ff46 100644 --- a/chart/templates/server-deployment.yaml +++ b/chart/templates/server-deployment.yaml @@ -170,6 +170,10 @@ spec: - name: ENABLE_LOCAL_APP value: "true" {{- end }} + {{- if $comp.enableOAuthServer }} + - name: ENABLE_OAUTH_SERVER + value: "true" + {{- end }} {{- if $comp.portAccessForUsersOnly }} - name: PORT_ACCESS_FOR_USERS_ONLY value: "true" @@ -216,6 +220,8 @@ spec: key: apikey - name: GITPOD_GARBAGE_COLLECTION_DISABLED value: {{ $comp.garbageCollection.disabled | default "false" | quote }} + - name: OAUTH_SERVER_JWT_SECRET + value: {{ (randAlphaNum 20) | quote }} {{- if $comp.serverContainer.env }} {{ toYaml $comp.serverContainer.env | indent 8 }} {{- end }} diff --git a/components/dashboard/public/complete-auth/index.html b/components/dashboard/public/complete-auth/index.html index 7e87db0dc735d6..c2478233670782 100644 --- a/components/dashboard/public/complete-auth/index.html +++ b/components/dashboard/public/complete-auth/index.html @@ -11,7 +11,12 @@ Done + + + If this tab is not closed automatically, feel free to close it and proceed. + +`) + } + + returnServer := &http.Server{ + Addr: "localhost:0", + Handler: http.HandlerFunc(returnHandler), + } + go func() { + err := returnServer.Serve(rl) + if err != nil { + errChan <- err + } + }() + defer returnServer.Shutdown(ctx) + + baseURL := gitpodHost + if baseURL == "" { + baseURL = "https://gitpod.io" + } + reqURL, err := url.Parse(baseURL) + if err != nil { + return err + } + authURL := *reqURL + authURL.Path = "/api/oauth/authorize" + tokenURL := *reqURL + tokenURL.Path = "/api/oauth/token" + conf := &oauth2.Config{ + ClientID: "gplctl-1.0", + ClientSecret: "gplctl-1.0-secret", // Required (even though it is marked as optional?!) + Scopes: []string{ + "function:getWorkspace", + "function:listenForWorkspaceInstanceUpdates", + "resource:workspace::*::get", + "resource:workspaceInstance::*::get", + }, + Endpoint: oauth2.Endpoint{ + AuthURL: authURL.String(), + TokenURL: tokenURL.String(), + }, + } + responseTypeParam := oauth2.SetAuthURLParam("response_type", "code") + redirectURIParam := oauth2.SetAuthURLParam("redirect_uri", fmt.Sprintf("http://localhost:%d", rl.Addr().(*net.TCPAddr).Port)) + codeChallengeMethodParam := oauth2.SetAuthURLParam("code_challenge_method", "S256") + codeVerifier := PkceVerifier(84) + codeChallengeParam := oauth2.SetAuthURLParam("code_challenge", PkceChallenge(codeVerifier)) + + // Redirect user to consent page to ask for permission + // for the scopes specified above. + authorizationURL := conf.AuthCodeURL("state", responseTypeParam, redirectURIParam, codeChallengeParam, codeChallengeMethodParam) + fmt.Printf("URL for the auth: %v\n", authorizationURL) + + // open a browser window to the authorizationURL + err = open.Start(authorizationURL) + if err != nil { + fmt.Printf("snap: can't open browser to URL %s: %s\n", authorizationURL, err) + os.Exit(1) + } + + var query url.Values + var code, approved string + select { + case <-ctx.Done(): + fmt.Printf("DONE: %v\n", ctx.Err()) + os.Exit(1) + case err = <-errChan: + fmt.Printf("ERR: %v\n", err) + os.Exit(1) + case query = <-queryChan: + code = query.Get("code") + approved = query.Get("approved") + } + fmt.Printf("code: %s, approved:%s\n", code, approved) + + if approved == "no" { + log.Println("Client approval was not granted... exiting") + os.Exit(1) + } + + // Use the authorization code that is pushed to the redirect + // URL. Exchange will do the handshake to retrieve the + // initial access token. + // We need to add the required PKCE param as well... + // NOTE: we do not currently support refreshing so using the client from conf.Client will fail when token expires (which technically it doesn't) + codeVerifierParam := oauth2.SetAuthURLParam("code_verifier", codeVerifier) + tok, err := conf.Exchange(ctx, code, codeVerifierParam, redirectURIParam) + if err != nil { + log.Fatal(err) + } + + log.Printf("TOK: %#v\n", tok) + + // Extract Gitpod token from OAuth token (JWT) + claims := jwt.MapClaims{} + parser := new(jwt.Parser) + gitpodToken, _, err := parser.ParseUnverified(tok.AccessToken, &claims) + if err != nil { + log.Fatal(err) + } + // gitpodToken, err := jwt.ParseWithClaims(tok.AccessToken, &claims, func(token *jwt.Token) (interface{}, error) { + // return []byte("secret secret secret"), nil + // }) + // if err != nil { + // log.Fatal(err) + // } + // if !gitpodToken.Valid { + // log.Println("OAuth2 token invalid... exiting") + // os.Exit(1) + // } + + log.Printf("Gitpod: %#v\n%#v\n", claims, gitpodToken) + + // client := conf.Client(ctx, tok) + // client.Get("...") + os.Exit(0) + return nil +} diff --git a/components/server/package.json b/components/server/package.json index b77e6ac260cc0d..b68c86e490f8ec 100644 --- a/components/server/package.json +++ b/components/server/package.json @@ -36,6 +36,7 @@ "@gitpod/licensor": "0.1.5", "@gitpod/ws-manager": "0.1.5", "@google-cloud/storage": "^5.6.0", + "@jmondi/oauth2-server": "^1.1.0", "@octokit/rest": "16.35.0", "@probot/get-private-key": "^1.1.0", "amqplib": "^0.5.2", diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index 327d3a67f69218..d63d3b881e9126 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -74,6 +74,7 @@ import { IDEPluginServiceClient } from '@gitpod/content-service/lib/ideplugin_gr import { GitTokenScopeGuesser } from './workspace/git-token-scope-guesser'; import { GitTokenValidator } from './workspace/git-token-validator'; import { newAnalyticsWriterFromEnv, IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics'; +import { OAuthController } from './oauth-server/oauth-controller'; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Env).toSelf().inSingletonScope(); @@ -188,4 +189,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(CodeSyncService).toSelf().inSingletonScope(); bind(IAnalyticsWriter).toDynamicValue(newAnalyticsWriterFromEnv).inSingletonScope(); + + bind(OAuthController).toSelf().inSingletonScope(); }); diff --git a/components/server/src/env.ts b/components/server/src/env.ts index 24dfb8660eb8b4..959d4a569484fb 100644 --- a/components/server/src/env.ts +++ b/components/server/src/env.ts @@ -86,6 +86,7 @@ export class Env extends AbstractComponentEnv { readonly devBranch = process.env.DEV_BRANCH || ''; readonly enableLocalApp = process.env.ENABLE_LOCAL_APP === "true"; + readonly enableOAuthServer = process.env.ENABLE_OAUTH_SERVER === "true"; readonly authProviderConfigs = this.parseAuthProviderParamss(); @@ -201,4 +202,5 @@ export class Env extends AbstractComponentEnv { readonly runDbDeleter: boolean = getEnvVar('RUN_DB_DELETER', 'false') === 'true'; + readonly oauthServerJWTSecret = getEnvVar("OAUTH_SERVER_JWT_SECRET") } diff --git a/components/server/src/oauth-server/db.ts b/components/server/src/oauth-server/db.ts new file mode 100644 index 00000000000000..e81839c3d74ec6 --- /dev/null +++ b/components/server/src/oauth-server/db.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { OAuthClient, OAuthScope, OAuthToken } from "@jmondi/oauth2-server"; +import { ScopedResourceGuard } from "../auth/resource-access"; + +/** +* Currently (2021-05-15) we only support 1 client and a fixed set of scopes so hard-coding here is acceptable. +* This will change in time, in which case we can move to using the DB. +*/ +export interface InMemory { + clients: { [id: string]: OAuthClient }; + tokens: { [id: string]: OAuthToken }; + scopes: { [id: string]: OAuthScope }; +} + +// Scopes +const getWorkspacesScope: OAuthScope = { name: "function:getWorkspaces" }; +const listenForWorkspaceInstanceUpdatesScope: OAuthScope = { name: "function:listenForWorkspaceInstanceUpdates" }; +const getWorkspaceResourceScope: OAuthScope = { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspace", subjectID: "*", operations: ["get"] }) }; +const getWorkspaceInstanceResourceScope: OAuthScope = { name: "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspaceInstance", subjectID: "*", operations: ["get"] }) }; + +// Clients +const localAppClientID = 'gplctl-1.0'; +const localClient: OAuthClient = { + id: localAppClientID, + secret: `${localAppClientID}-secret`, + name: 'Gitpod local control client', + // TODO(rl) - allow port range/external specification + redirectUris: ['http://localhost:64110'], + allowedGrants: ['authorization_code'], + scopes: [getWorkspacesScope, listenForWorkspaceInstanceUpdatesScope, getWorkspaceResourceScope, getWorkspaceInstanceResourceScope], +} + +export const inMemoryDatabase: InMemory = { + clients: { + [localClient.id]: localClient, + }, + tokens: {}, + scopes: { + [getWorkspacesScope.name]: getWorkspacesScope, + [listenForWorkspaceInstanceUpdatesScope.name]: listenForWorkspaceInstanceUpdatesScope, + [getWorkspaceResourceScope.name]: getWorkspaceResourceScope, + [getWorkspaceInstanceResourceScope.name]: getWorkspaceInstanceResourceScope, + }, +}; diff --git a/components/server/src/oauth-server/oauth-authorization-server.ts b/components/server/src/oauth-server/oauth-authorization-server.ts new file mode 100644 index 00000000000000..61ea6f117a18c5 --- /dev/null +++ b/components/server/src/oauth-server/oauth-authorization-server.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { AuthorizationServer, DateInterval, JwtService, OAuthAuthCodeRepository, OAuthTokenRepository, OAuthUserRepository } from "@jmondi/oauth2-server"; +import { + inMemoryClientRepository, + inMemoryScopeRepository +} from "./repository"; + +export const clientRepository = inMemoryClientRepository; +const scopeRepository = inMemoryScopeRepository; + +export function createAuthorizationServer(authCodeRepository: OAuthAuthCodeRepository, userRepository: OAuthUserRepository, tokenRepository: OAuthTokenRepository, jwtSecret: string): AuthorizationServer { + const authorizationServer = new AuthorizationServer( + authCodeRepository, + clientRepository, + tokenRepository, + scopeRepository, + userRepository, + new JwtService(jwtSecret), + { + // Be explicit, communicate intent. Default is true but let's not assume that + requiresPKCE: true, + } + ); + + authorizationServer.enableGrantType("authorization_code", new DateInterval('5m')); + return authorizationServer; +} diff --git a/components/server/src/oauth-server/oauth-controller.ts b/components/server/src/oauth-server/oauth-controller.ts new file mode 100644 index 00000000000000..b54d003e6a9280 --- /dev/null +++ b/components/server/src/oauth-server/oauth-controller.ts @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { AuthCodeRepositoryDB } from '@gitpod/gitpod-db/lib/typeorm/auth-code-repository-db'; +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 * as express from 'express'; +import { inject, injectable } from "inversify"; +import { Env } from "../env"; +import { clientRepository, createAuthorizationServer } from './oauth-authorization-server'; + +@injectable() +export class OAuthController { + @inject(Env) protected readonly env: Env; + @inject(UserDB) protected readonly userDb: UserDB; + @inject(AuthCodeRepositoryDB) protected readonly authCodeRepositoryDb: AuthCodeRepositoryDB; + + private getValidUser(req: express.Request, res: express.Response): User | null { + if (!req.isAuthenticated() || !User.is(req.user)) { + const redirectTarget = encodeURIComponent(`${this.env.hostUrl}api${req.originalUrl}`); + const redirectTo = `${this.env.hostUrl}login?returnTo=${redirectTarget}`; + res.redirect(redirectTo) + return null; + } + const user = req.user as User; + if (!user) { + res.sendStatus(500); + return null; + } + if (user.blocked) { + res.sendStatus(403); + return null; + } + return user; + } + + private async hasApproval(user: User, clientID: string, req: express.Request, res: express.Response): Promise { + // Have they just authorized, or not, the local-app? + const wasApproved = req.query['approved'] || ''; + if (wasApproved === 'no') { + const additionalData = user?.additionalData; + if (additionalData && additionalData.oauthClientsApproved) { + delete additionalData.oauthClientsApproved[clientID]; + await this.userDb.updateUserPartial(user); + } + + // Let the local app know they rejected the approval + const rt = req.query.redirect_uri; + if (!rt || !rt.startsWith("http://localhost:")) { + log.error(`/oauth/authorize: invalid returnTo URL: "${rt}"`) + res.sendStatus(400); + return false; + } + res.redirect(`${rt}/?approved=no`); + return false; + } else if (wasApproved == 'yes') { + const additionalData = user.additionalData = user.additionalData || {}; + additionalData.oauthClientsApproved = { + ...additionalData.oauthClientsApproved, + [clientID]: new Date().toISOString() + } + await this.userDb.updateUserPartial(user); + } else { + const oauthClientsApproved = user?.additionalData?.oauthClientsApproved; + if (!oauthClientsApproved || !oauthClientsApproved[clientID]) { + const client = await clientRepository.getByIdentifier(clientID) + if (client) { + const redirectTarget = encodeURIComponent(`${this.env.hostUrl}api${req.originalUrl}`); + const redirectTo = `${this.env.hostUrl}oauth-approval?clientID=${client.id}&clientName=${client.name}&returnTo=${redirectTarget}`; + res.redirect(redirectTo) + return false; + } else { + log.error(`/oauth/authorize unknown client id: "${clientID}"`) + res.sendStatus(400); + return false; + } + } + } + return true; + } + + get oauthRouter(): express.Router { + const router = express.Router(); + if (!this.env.enableOAuthServer) { + log.warn('OAuth server disabled!') + return router; + } + + const authorizationServer = createAuthorizationServer(this.authCodeRepositoryDb, this.userDb, this.userDb, this.env.oauthServerJWTSecret); + router.get("/oauth/authorize", async (req: express.Request, res: express.Response) => { + const clientID = req.query.client_id; + if (!clientID) { + res.sendStatus(400); + return false; + } + + const user = this.getValidUser(req, res); + if (!user) { + return; + } + + // Check for approval of this client + if (!this.hasApproval(user, clientID, req, res)) { + return; + } + + const request = new OAuthRequest(req); + + try { + // Validate the HTTP request and return an AuthorizationRequest object. + const authRequest = await authorizationServer.validateAuthorizationRequest(request); + + // Once the user has logged in set the user on the AuthorizationRequest + authRequest.user = { id: user.id } + + // The user has approved the client so update the status + authRequest.isAuthorizationApproved = true; + + // Return the HTTP redirect response + const oauthResponse = await authorizationServer.completeAuthorizationRequest(authRequest); + return handleResponse(req, res, oauthResponse); + } catch (e) { + handleError(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); + } catch (e) { + handleError(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; + } +} \ No newline at end of file diff --git a/components/server/src/oauth-server/repository.ts b/components/server/src/oauth-server/repository.ts new file mode 100644 index 00000000000000..8311ed3932015a --- /dev/null +++ b/components/server/src/oauth-server/repository.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; +import { GrantIdentifier, OAuthClient, OAuthClientRepository, OAuthScope, OAuthScopeRepository } from "@jmondi/oauth2-server"; +import { inMemoryDatabase } from "./db"; + +/** +* Currently (2021-05-15) we only support 1 client and a fixed set of scopes so using in-memory here is acceptable. +* This will change in time, in which case we can move to using the DB. +*/ +export const inMemoryClientRepository: OAuthClientRepository = { + async getByIdentifier(clientId: string): Promise { + return inMemoryDatabase.clients[clientId]; + }, + + async isClientValid(grantType: GrantIdentifier, client: OAuthClient, clientSecret?: string): Promise { + if (client.secret !== clientSecret) { + log.warn(`isClientValid: bad secret`) + return false; + } + + if (!client.allowedGrants.includes(grantType)) { + log.warn(`isClientValid: bad grant`) + return false; + } + + return true; + }, +}; + +export const inMemoryScopeRepository: OAuthScopeRepository = { + async getAllByIdentifiers(scopeNames: string[]): Promise { + return Object.values(inMemoryDatabase.scopes).filter(scope => scopeNames.includes(scope.name)); + }, + async finalize( + scopes: OAuthScope[], + identifier: GrantIdentifier, + client: OAuthClient, + user_id?: string, + ): Promise { + return scopes; + }, +}; diff --git a/components/server/src/server.ts b/components/server/src/server.ts index d3f461aa1aca3c..b760baafe3e6d3 100644 --- a/components/server/src/server.ts +++ b/components/server/src/server.ts @@ -39,6 +39,7 @@ import { BearerAuth } from './auth/bearer-authenticator'; import { HostContextProvider } from './auth/host-context-provider'; import { CodeSyncService } from './code-sync/code-sync-service'; import { increaseHttpRequestCounter, observeHttpRequestDuration } from './prometheus-metrics'; +import { OAuthController } from './oauth-server/oauth-controller'; @injectable() export class Server { @@ -67,6 +68,7 @@ export class Server { @inject(BearerAuth) protected readonly bearerAuth: BearerAuth; @inject(HostContextProvider) protected readonly hostCtxProvider: HostContextProvider; + @inject(OAuthController) protected readonly oauthController: OAuthController; protected readonly eventEmitter = new EventEmitter(); protected app?: express.Application; @@ -254,6 +256,7 @@ export class Server { app.use("/version", (req: express.Request, res: express.Response, next: express.NextFunction) => { res.send(this.env.version); }); + app.use(this.oauthController.oauthRouter); } protected isAnsweredRequest(req: express.Request, res: express.Response) { diff --git a/components/server/src/user/user-controller.ts b/components/server/src/user/user-controller.ts index 5c0f04caf4a1d3..7bde880313787e 100644 --- a/components/server/src/user/user-controller.ts +++ b/components/server/src/user/user-controller.ts @@ -77,6 +77,7 @@ export class UserController { const returnTo = this.getSafeReturnToParam(req); const search = returnTo ? `returnTo=${returnTo}` : ''; const loginPageUrl = this.env.hostUrl.asLogin().with({ search }).toString(); + log.info(`Redirecting to login ${loginPageUrl}`) res.redirect(loginPageUrl); return; } @@ -85,7 +86,7 @@ export class UserController { try { await saveSession(req); } catch (error) { - increaseLoginCounter("failed", "unkown") + increaseLoginCounter("failed", "unknown") log.error(`Login failed due to session save error; redirecting to /sorry`, { req, error, clientInfo }); res.redirect(this.getSorryUrl("Login failed 🦄 Please try again")); } @@ -255,8 +256,8 @@ export class UserController { scopes: [ "function:getWorkspaces", "function:listenForWorkspaceInstanceUpdates", - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "workspace", subjectID: "*", operations: ["get"]}), - "resource:"+ScopedResourceGuard.marshalResourceScope({kind: "workspaceInstance", subjectID: "*", operations: ["get"]}), + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspace", subjectID: "*", operations: ["get"] }), + "resource:" + ScopedResourceGuard.marshalResourceScope({ kind: "workspaceInstance", subjectID: "*", operations: ["get"] }), ], created: new Date().toISOString(), }; @@ -452,7 +453,7 @@ export class UserController { return; } - // The user has accepted the terms. + // The user has approved the terms. log.info(logContext, '(TOS) User did agree.', logPayload); if (TosFlow.WithIdentity.is(tosFlowInfo)) { diff --git a/gitpod-ws.code-workspace b/gitpod-ws.code-workspace index 31ab15c6f0fba1..0657eae2a77be8 100644 --- a/gitpod-ws.code-workspace +++ b/gitpod-ws.code-workspace @@ -10,6 +10,7 @@ { "path": "components/gitpod-cli" }, { "path": "components/image-builder" }, { "path": "components/licensor" }, + { "path": "components/local-app" }, { "path": "components/registry-facade" }, { "path": "components/service-waiter" }, { "path": "components/supervisor" }, diff --git a/yarn.lock b/yarn.lock index 949c8542995076..e09a5038fe7295 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3341,6 +3341,15 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jmondi/oauth2-server@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@jmondi/oauth2-server/-/oauth2-server-1.1.0.tgz#37014c4aceaee9b4559df224a4d3a743e66e5929" + integrity sha512-7UuliIJVnn3ISVEQ/CeRdKdY6gQb+RbOAlCZysdWytcvWNF+5Xb32ARbjOnzzLRzlh5ZxAKC5Zta0TZSKeXchg== + dependencies: + jsonwebtoken "^8.5.1" + ms "^2.1.3" + uri-js "^4.4.1" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -14675,7 +14684,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -20619,6 +20628,13 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"