Skip to content

Commit f5c0cb3

Browse files
committed
[dummy] auth
1 parent 51f9087 commit f5c0cb3

File tree

3 files changed

+43
-26
lines changed

3 files changed

+43
-26
lines changed

components/server/src/api/dummy.ts

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

7-
import { HandlerContext, ServiceImpl } from "@bufbuild/connect";
7+
import { Code, ConnectError, HandlerContext, ServiceImpl } from "@bufbuild/connect";
8+
import { User } from "@gitpod/gitpod-protocol";
89
import { HelloService } from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_connectweb";
910
import {
1011
LotsOfRepliesRequest,
1112
LotsOfRepliesResponse,
1213
SayHelloRequest,
1314
SayHelloResponse,
1415
} from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_pb";
15-
import { injectable } from "inversify";
16+
import { inject, injectable } from "inversify";
17+
import { SessionHandler } from "../session-handler";
1618

1719
/**
1820
* TODO(ak):
19-
* - auth
2021
* - server-side observability
2122
* - client-side observability
2223
* - rate limitting
@@ -27,25 +28,40 @@ import { injectable } from "inversify";
2728
*/
2829
@injectable()
2930
export class APIHelloService implements ServiceImpl<typeof HelloService> {
31+
constructor(
32+
@inject(SessionHandler)
33+
private readonly sessionHandler: SessionHandler,
34+
) {}
35+
3036
async sayHello(req: SayHelloRequest, context: HandlerContext): Promise<SayHelloResponse> {
37+
const user = await this.authUser(context);
3138
const response = new SayHelloResponse();
32-
response.reply = "Hello " + this.getSubject();
39+
response.reply = "Hello " + this.getSubject(user);
3340
return response;
3441
}
3542
async *lotsOfReplies(req: LotsOfRepliesRequest, context: HandlerContext): AsyncGenerator<LotsOfRepliesResponse> {
43+
const user = await this.authUser(context);
3644
let count = req.previousCount || 0;
3745
while (true) {
3846
const response = new LotsOfRepliesResponse();
39-
response.reply = `Hello ${this.getSubject()} ${count}`;
47+
response.reply = `Hello ${this.getSubject(user)} ${count}`;
4048
response.count = count;
4149
yield response;
4250
count++;
4351
await new Promise((resolve) => setTimeout(resolve, 30000));
4452
}
4553
}
4654

47-
private getSubject(): string {
48-
// TODO(ak) get identify from JWT
49-
return "World";
55+
private getSubject(user: User): string {
56+
return User.getName(user) || "World";
57+
}
58+
59+
// TODO(ak) decorate
60+
private async authUser(context: HandlerContext) {
61+
const user = await this.sessionHandler.verify(context.requestHeader.get("cookie"));
62+
if (!user) {
63+
throw new ConnectError("unauthenticated", Code.Unauthenticated);
64+
}
65+
return user;
5066
}
5167
}

components/server/src/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ export class Server {
307307
log.info("Registered Bitbucket Server app at " + BitbucketServerApp.path);
308308
app.use(BitbucketServerApp.path, this.bitbucketServerApp.router);
309309

310+
// TODO(ak) move to own app on port 3001
310311
app.use(this.api.apiRouter);
311312
}
312313

components/server/src/session-handler.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import express from "express";
88
import { inject, injectable } from "inversify";
99
import websocket from "ws";
1010

11+
import { User } from "@gitpod/gitpod-protocol";
1112
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1213
import { AuthJWT } from "./auth/jwt";
1314
import { Config } from "./config";
@@ -90,38 +91,37 @@ export class SessionHandler {
9091
// On failure, the next handler is called and the `req.user` is not set. Some APIs/Websocket RPCs do
9192
// not require authentication, and as such we cannot fail the request at this stage.
9293
protected async handler(req: express.Request, next: express.NextFunction): Promise<void> {
93-
const cookies = parseCookieHeader(req.headers.cookie || "");
94+
const user = await this.verify(req.headers.cookie || "");
95+
if (user) {
96+
// We set the user object on the request to signal the user is authenticated.
97+
// Passport uses the `user` property on the request to determine if the session
98+
// is authenticated.
99+
req.user = user;
100+
}
101+
102+
next();
103+
}
104+
105+
async verify(cookie: string): Promise<User | undefined> {
106+
const cookies = parseCookieHeader(cookie);
94107
const jwtToken = cookies[this.getJWTCookieName(this.config)];
95108
if (!jwtToken) {
96109
log.debug("No JWT session present on request");
97-
next();
98-
return;
110+
return undefined;
99111
}
100-
101112
try {
102113
const claims = await this.authJWT.verify(jwtToken);
103-
log.debug("JWT Session token verified", {
104-
claims,
105-
});
114+
log.debug("JWT Session token verified", { claims });
106115

107116
const subject = claims.sub;
108117
if (!subject) {
109118
throw new Error("Subject is missing from JWT session claims");
110119
}
111120

112-
const user = await this.userService.findUserById(subject, subject);
113-
114-
// We set the user object on the request to signal the user is authenticated.
115-
// Passport uses the `user` property on the request to determine if the session
116-
// is authenticated.
117-
req.user = user;
118-
119-
// Trigger the next middleware in the chain.
120-
next();
121+
return await this.userService.findUserById(subject, subject);
121122
} catch (err) {
122123
log.warn("Failed to authenticate user with JWT Session", err);
123-
// Remove the existing cookie, to force the user to re-sing in, and hence refresh it
124-
next();
124+
return undefined;
125125
}
126126
}
127127

0 commit comments

Comments
 (0)