Skip to content

Commit 96b7a02

Browse files
committed
[server] Introduce RequestContext
1 parent 0e00e3d commit 96b7a02

26 files changed

+554
-225
lines changed

components/gitpod-protocol/src/analytics.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
export const IAnalyticsWriter = Symbol("IAnalyticsWriter");
88

9-
type Identity =
10-
| { userId: string | number; anonymousId?: string | number }
11-
| { userId?: string | number; anonymousId: string | number };
9+
type Identity = { userId?: string | number; anonymousId?: string | number; subjectId?: string };
1210

1311
interface Message {
1412
messageId?: string;

components/gitpod-protocol/src/messaging/error.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export const ErrorCodes = {
8989
// 404 Not Found
9090
NOT_FOUND: 404 as const,
9191

92+
// 408 Request Timeout
93+
REQUEST_TIMEOUT: 408 as const,
94+
9295
// 409 Conflict (e.g. already existing)
9396
CONFLICT: 409 as const,
9497

components/gitpod-protocol/src/util/logging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface LogContext {
1717
organizationId?: string;
1818
sessionId?: string;
1919
userId?: string;
20+
subjectId?: string;
2021
workspaceId?: string;
2122
instanceId?: string;
2223
}

components/server/src/analytics.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
*/
66
import { User } from "@gitpod/gitpod-protocol";
77
import { Request } from "express";
8-
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
8+
import { IAnalyticsWriter, IdentifyMessage, PageMessage, TrackMessage } from "@gitpod/gitpod-protocol/lib/analytics";
99
import * as crypto from "crypto";
1010
import { clientIp } from "./express-util";
11+
import { ctxTrySubjectId } from "./util/request-context";
1112

1213
export async function trackLogin(user: User, request: Request, authHost: string, analytics: IAnalyticsWriter) {
1314
// make new complete identify call for each login
@@ -129,3 +130,31 @@ function stripCookie(cookie: string) {
129130
return cookie;
130131
}
131132
}
133+
134+
export class ContextAwareAnalyticsWriter implements IAnalyticsWriter {
135+
constructor(readonly writer: IAnalyticsWriter) {}
136+
137+
identify(msg: IdentifyMessage): void {}
138+
139+
track(msg: TrackMessage): void {}
140+
141+
page(msg: PageMessage): void {
142+
const traceIds = this.getAnalyticsIds();
143+
this.writer.page({
144+
...msg,
145+
userId: msg.userId || traceIds.userId,
146+
subjectId: msg.subjectId || traceIds.subjectId,
147+
});
148+
}
149+
150+
private getAnalyticsIds(): { userId?: string; subjectId?: string } {
151+
const subjectId = ctxTrySubjectId();
152+
if (!subjectId) {
153+
return {};
154+
}
155+
return {
156+
userId: subjectId.userId(),
157+
subjectId: subjectId.toString(),
158+
};
159+
}
160+
}

components/server/src/api/auth-provider-service-api.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,24 @@ import {
2323
DeleteAuthProviderResponse,
2424
} from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
2525
import { AuthProviderService } from "../auth/auth-provider-service";
26-
import { AuthProviderEntry, AuthProviderInfo } from "@gitpod/gitpod-protocol";
26+
import { AuthProviderEntry, AuthProviderInfo, User } from "@gitpod/gitpod-protocol";
2727
import { Unauthenticated } from "./unauthenticated";
2828
import { validate as uuidValidate } from "uuid";
2929
import { selectPage } from "./pagination";
30+
import { ctxUserId } from "../util/request-context";
31+
import { UserService } from "../user/user-service";
3032

3133
@injectable()
3234
export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderServiceInterface> {
3335
constructor(
3436
@inject(PublicAPIConverter) private readonly apiConverter: PublicAPIConverter,
3537
@inject(AuthProviderService) private readonly authProviderService: AuthProviderService,
38+
@inject(UserService) private readonly userService: UserService,
3639
) {}
3740

3841
async createAuthProvider(
3942
request: CreateAuthProviderRequest,
40-
context: HandlerContext,
43+
_: HandlerContext,
4144
): Promise<CreateAuthProviderResponse> {
4245
const ownerId = request.owner.case === "ownerId" ? request.owner.value : "";
4346
const organizationId = request.owner.case === "organizationId" ? request.owner.value : "";
@@ -47,20 +50,20 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
4750
}
4851

4952
if (organizationId) {
50-
const result = await this.authProviderService.createOrgAuthProvider(context.user.id, {
53+
const result = await this.authProviderService.createOrgAuthProvider(ctxUserId(), {
5154
organizationId,
5255
host: request.host,
53-
ownerId: context.user.id,
56+
ownerId: ctxUserId(),
5457
type: this.apiConverter.fromAuthProviderType(request.type),
5558
clientId: request.oauth2Config?.clientId,
5659
clientSecret: request.oauth2Config?.clientSecret,
5760
});
5861

5962
return new CreateAuthProviderResponse({ authProvider: this.apiConverter.toAuthProvider(result) });
6063
} else {
61-
const result = await this.authProviderService.createAuthProviderOfUser(context.user.id, {
64+
const result = await this.authProviderService.createAuthProviderOfUser(ctxUserId(), {
6265
host: request.host,
63-
ownerId: context.user.id,
66+
ownerId: ctxUserId(),
6467
type: this.apiConverter.fromAuthProviderType(request.type),
6568
clientId: request.oauth2Config?.clientId,
6669
clientSecret: request.oauth2Config?.clientSecret,
@@ -69,12 +72,12 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
6972
return new CreateAuthProviderResponse({ authProvider: this.apiConverter.toAuthProvider(result) });
7073
}
7174
}
72-
async getAuthProvider(request: GetAuthProviderRequest, context: HandlerContext): Promise<GetAuthProviderResponse> {
75+
async getAuthProvider(request: GetAuthProviderRequest, _: HandlerContext): Promise<GetAuthProviderResponse> {
7376
if (!request.authProviderId) {
7477
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
7578
}
7679

77-
const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId);
80+
const authProvider = await this.authProviderService.getAuthProvider(ctxUserId(), request.authProviderId);
7881
if (!authProvider) {
7982
throw new ConnectError("Provider not found.", Code.NotFound);
8083
}
@@ -86,7 +89,7 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
8689

8790
async listAuthProviders(
8891
request: ListAuthProvidersRequest,
89-
context: HandlerContext,
92+
_: HandlerContext,
9093
): Promise<ListAuthProvidersResponse> {
9194
const target = request.id;
9295
const ownerId = target.case === "userId" ? target.value : "";
@@ -97,8 +100,8 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
97100
}
98101

99102
const authProviders = organizationId
100-
? await this.authProviderService.getAuthProvidersOfOrg(context.user.id, organizationId)
101-
: await this.authProviderService.getAuthProvidersOfUser(context.user.id);
103+
? await this.authProviderService.getAuthProvidersOfOrg(ctxUserId(), organizationId)
104+
: await this.authProviderService.getAuthProvidersOfUser(ctxUserId());
102105

103106
const selectedProviders = selectPage(authProviders, request.pagination);
104107
const redacted = selectedProviders.map(AuthProviderEntry.redact.bind(AuthProviderEntry));
@@ -118,9 +121,13 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
118121
@Unauthenticated()
119122
async listAuthProviderDescriptions(
120123
request: ListAuthProviderDescriptionsRequest,
121-
context: HandlerContext,
124+
_: HandlerContext,
122125
): Promise<ListAuthProviderDescriptionsResponse> {
123-
const user = context.user;
126+
const userId = ctxUserId();
127+
let user: User | undefined = undefined;
128+
if (userId) {
129+
user = await this.userService.findUserById(userId, userId);
130+
}
124131
const aps = user
125132
? await this.authProviderService.getAuthProviderDescriptions(user)
126133
: await this.authProviderService.getAuthProviderDescriptionsUnauthenticated();
@@ -135,7 +142,7 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
135142

136143
async updateAuthProvider(
137144
request: UpdateAuthProviderRequest,
138-
context: HandlerContext,
145+
_: HandlerContext,
139146
): Promise<UpdateAuthProviderResponse> {
140147
if (!request.authProviderId) {
141148
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
@@ -146,23 +153,23 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
146153
throw new ConnectError("clientId or clientSecret are required", Code.InvalidArgument);
147154
}
148155

149-
const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId);
156+
const authProvider = await this.authProviderService.getAuthProvider(ctxUserId(), request.authProviderId);
150157
if (!authProvider) {
151158
throw new ConnectError("Provider not found.", Code.NotFound);
152159
}
153160

154161
let entry: AuthProviderEntry;
155162
if (authProvider.organizationId) {
156-
entry = await this.authProviderService.updateOrgAuthProvider(context.user.id, {
163+
entry = await this.authProviderService.updateOrgAuthProvider(ctxUserId(), {
157164
id: request.authProviderId,
158165
organizationId: authProvider.organizationId,
159166
clientId: clientId,
160167
clientSecret: clientSecret,
161168
});
162169
} else {
163-
entry = await this.authProviderService.updateAuthProviderOfUser(context.user.id, {
170+
entry = await this.authProviderService.updateAuthProviderOfUser(ctxUserId(), {
164171
id: request.authProviderId,
165-
ownerId: context.user.id,
172+
ownerId: ctxUserId(),
166173
clientId: clientId,
167174
clientSecret: clientSecret,
168175
});
@@ -175,25 +182,25 @@ export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderSe
175182

176183
async deleteAuthProvider(
177184
request: DeleteAuthProviderRequest,
178-
context: HandlerContext,
185+
_: HandlerContext,
179186
): Promise<DeleteAuthProviderResponse> {
180187
if (!request.authProviderId) {
181188
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
182189
}
183190

184-
const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId);
191+
const authProvider = await this.authProviderService.getAuthProvider(ctxUserId(), request.authProviderId);
185192
if (!authProvider) {
186193
throw new ConnectError("Provider not found.", Code.NotFound);
187194
}
188195

189196
if (authProvider.organizationId) {
190197
await this.authProviderService.deleteAuthProviderOfOrg(
191-
context.user.id,
198+
ctxUserId(),
192199
authProvider.organizationId,
193200
request.authProviderId,
194201
);
195202
} else {
196-
await this.authProviderService.deleteAuthProviderOfUser(context.user.id, request.authProviderId);
203+
await this.authProviderService.deleteAuthProviderOfUser(ctxUserId(), request.authProviderId);
197204
}
198205

199206
return new DeleteAuthProviderResponse();

components/server/src/api/configuration-service-api.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
2121
import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";
2222
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
23+
import { ctxUserId } from "../util/request-context";
24+
import { UserService } from "../user/user-service";
2325

2426
@injectable()
2527
export class ConfigurationServiceAPI implements ServiceImpl<typeof ConfigurationServiceInterface> {
@@ -28,11 +30,13 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
2830
private readonly projectService: ProjectsService,
2931
@inject(PublicAPIConverter)
3032
private readonly apiConverter: PublicAPIConverter,
33+
@inject(UserService)
34+
private readonly userService: UserService,
3135
) {}
3236

3337
async createConfiguration(
3438
req: CreateConfigurationRequest,
35-
context: HandlerContext,
39+
_: HandlerContext,
3640
): Promise<CreateConfigurationResponse> {
3741
if (!req.organizationId) {
3842
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organization_id is required");
@@ -44,6 +48,11 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
4448
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "name is required");
4549
}
4650

51+
const installer = await this.userService.findUserById(ctxUserId(), ctxUserId());
52+
if (!installer) {
53+
throw new ApplicationError(ErrorCodes.NOT_FOUND, "user not found");
54+
}
55+
4756
const project = await this.projectService.createProject(
4857
{
4958
teamId: req.organizationId,
@@ -52,35 +61,35 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
5261
appInstallationId: "",
5362
slug: "",
5463
},
55-
context.user,
64+
installer,
5665
);
5766

5867
return new CreateConfigurationResponse({
5968
configuration: this.apiConverter.toConfiguration(project),
6069
});
6170
}
6271

63-
async getConfiguration(req: GetConfigurationRequest, context: HandlerContext) {
72+
async getConfiguration(req: GetConfigurationRequest, _: HandlerContext) {
6473
if (!req.configurationId) {
6574
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configuration_id is required");
6675
}
6776

68-
const project = await this.projectService.getProject(context.user.id, req.configurationId);
77+
const project = await this.projectService.getProject(ctxUserId(), req.configurationId);
6978

7079
return {
7180
configuration: this.apiConverter.toConfiguration(project),
7281
};
7382
}
7483

75-
async listConfigurations(req: ListConfigurationsRequest, context: HandlerContext) {
84+
async listConfigurations(req: ListConfigurationsRequest, _: HandlerContext) {
7685
if (!req.organizationId) {
7786
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organization_id is required");
7887
}
7988

8089
const limit = req.pagination?.pageSize || 25;
8190
const offset = (req.pagination?.page ?? 0) * limit;
8291

83-
const { rows, total } = await this.projectService.findProjects(context.user.id, {
92+
const { rows, total } = await this.projectService.findProjects(ctxUserId(), {
8493
searchTerm: req.searchTerm,
8594
orderBy: "name",
8695
orderDir: "ASC",
@@ -96,12 +105,12 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
96105
});
97106
}
98107

99-
async deleteConfiguration(req: DeleteConfigurationRequest, handler: HandlerContext) {
108+
async deleteConfiguration(req: DeleteConfigurationRequest, _: HandlerContext) {
100109
if (!req.configurationId) {
101110
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configuration_id is required");
102111
}
103112

104-
await this.projectService.deleteProject(handler.user.id, req.configurationId);
113+
await this.projectService.deleteProject(ctxUserId(), req.configurationId);
105114

106115
return new DeleteConfigurationResponse();
107116
}

components/server/src/api/handler-context-augmentation.d.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

components/server/src/api/hello-service-api.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import { HandlerContext, ServiceImpl } from "@connectrpc/connect";
8-
import { User } from "@gitpod/gitpod-protocol";
98
import { HelloService } from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_connect";
109
import {
1110
LotsOfRepliesRequest,
@@ -14,27 +13,30 @@ import {
1413
SayHelloResponse,
1514
} from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_pb";
1615
import { injectable } from "inversify";
16+
import { ctxCheckAborted, ctxTrySubjectId } from "../util/request-context";
1717

1818
@injectable()
1919
export class HelloServiceAPI implements ServiceImpl<typeof HelloService> {
20-
async sayHello(req: SayHelloRequest, context: HandlerContext): Promise<SayHelloResponse> {
20+
async sayHello(req: SayHelloRequest, _: HandlerContext): Promise<SayHelloResponse> {
2121
const response = new SayHelloResponse();
22-
response.reply = "Hello " + this.getSubject(context);
22+
response.reply = "Hello " + getSubject();
2323
return response;
2424
}
25-
async *lotsOfReplies(req: LotsOfRepliesRequest, context: HandlerContext): AsyncGenerator<LotsOfRepliesResponse> {
25+
async *lotsOfReplies(req: LotsOfRepliesRequest, _: HandlerContext): AsyncGenerator<LotsOfRepliesResponse> {
2626
let count = req.previousCount || 0;
27-
while (!context.signal.aborted) {
27+
while (true) {
28+
ctxCheckAborted();
29+
2830
const response = new LotsOfRepliesResponse();
29-
response.reply = `Hello ${this.getSubject(context)} ${count}`;
31+
response.reply = `Hello ${getSubject()} ${count}`;
3032
response.count = count;
3133
yield response;
3234
count++;
3335
await new Promise((resolve) => setTimeout(resolve, 30000));
3436
}
3537
}
38+
}
3639

37-
private getSubject(context: HandlerContext): string {
38-
return User.getName(context.user) || "World";
39-
}
40+
function getSubject(): string {
41+
return ctxTrySubjectId()?.toString() || "World";
4042
}

0 commit comments

Comments
 (0)