Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/gitpod-protocol/src/protocol.ts
2498 views
1
/**
2
* Copyright (c) 2020 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { WorkspaceInstance, PortVisibility, PortProtocol, WorkspaceInstanceMetrics } from "./workspace-instance";
8
import { RoleOrPermission } from "./permission";
9
import { Project } from "./teams-projects-protocol";
10
import { createHash } from "crypto";
11
import { WorkspaceRegion } from "./workspace-cluster";
12
13
export interface UserInfo {
14
name?: string;
15
}
16
17
export interface User {
18
/** The user id */
19
id: string;
20
21
/** The ID of the Organization this user is owned by. If undefined, the user is owned by the installation */
22
organizationId?: string;
23
24
/** The timestamp when the user entry was created */
25
creationDate: string;
26
27
avatarUrl?: string;
28
29
name?: string;
30
31
/** Optional for backwards compatibility */
32
fullName?: string;
33
34
identities: Identity[];
35
36
/**
37
* Whether the user has been blocked to use our service, because of TOS violation for example.
38
* Optional for backwards compatibility.
39
*/
40
blocked?: boolean;
41
42
/** A map of random settings that alter the behaviour of Gitpod on a per-user basis */
43
featureFlags?: UserFeatureSettings;
44
45
/** The permissions and/or roles the user has */
46
rolesOrPermissions?: RoleOrPermission[];
47
48
/** Whether the user is logical deleted. This flag is respected by all queries in UserDB */
49
markedDeleted?: boolean;
50
51
additionalData?: AdditionalUserData;
52
53
// Identifies an explicit team or user ID to which all the user's workspace usage should be attributed to (e.g. for billing purposes)
54
usageAttributionId?: string;
55
56
// The last time this user got verified somehow. The user is not verified if this is empty.
57
lastVerificationTime?: string;
58
59
// The phone number used for the last phone verification.
60
verificationPhoneNumber?: string;
61
62
// The FGA relationships version of this user
63
fgaRelationshipsVersion?: number;
64
}
65
66
export namespace User {
67
export function is(data: any): data is User {
68
return data && data.hasOwnProperty("id") && data.hasOwnProperty("identities");
69
}
70
export function getIdentity(user: User, authProviderId: string): Identity | undefined {
71
return user.identities.find((id) => id.authProviderId === authProviderId);
72
}
73
74
export function isOrganizationOwned(user: User) {
75
return !!user.organizationId;
76
}
77
}
78
79
export interface WorkspaceTimeoutSetting {
80
// user globol workspace timeout
81
workspaceTimeout: string;
82
// control whether to enable the closed timeout of a workspace, i.e. close web ide, disconnect ssh connection
83
disabledClosedTimeout: boolean;
84
}
85
86
export interface AdditionalUserData extends Partial<WorkspaceTimeoutSetting> {
87
/** @deprecated unused */
88
platforms?: UserPlatform[];
89
emailNotificationSettings?: EmailNotificationSettings;
90
featurePreview?: boolean;
91
ideSettings?: IDESettings;
92
// key is the name of the news, string the iso date when it was seen
93
whatsNewSeen?: { [key: string]: string };
94
// key is the name of the OAuth client i.e. local app, string the iso date when it was approved
95
// TODO(rl): provide a management UX to allow rescinding of approval
96
oauthClientsApproved?: { [key: string]: string };
97
// to remember GH Orgs the user installed/updated the GH App for
98
/** @deprecated unused */
99
knownGitHubOrgs?: string[];
100
// Git clone URL pointing to the user's dotfile repo
101
dotfileRepo?: string;
102
// preferred workspace classes
103
workspaceClasses?: WorkspaceClasses;
104
// additional user profile data
105
profile?: ProfileDetails;
106
/** @deprecated */
107
shouldSeeMigrationMessage?: boolean;
108
// remembered workspace auto start options
109
workspaceAutostartOptions?: WorkspaceAutostartOption[];
110
}
111
112
export interface WorkspaceAutostartOption {
113
cloneURL: string;
114
organizationId: string;
115
workspaceClass?: string;
116
ideSettings?: IDESettings;
117
region?: WorkspaceRegion;
118
}
119
120
export namespace AdditionalUserData {
121
export function set(user: User, partialData: Partial<AdditionalUserData>): User {
122
if (!user.additionalData) {
123
user.additionalData = {
124
...partialData,
125
};
126
} else {
127
user.additionalData = {
128
...user.additionalData,
129
...partialData,
130
};
131
}
132
return user;
133
}
134
}
135
136
// The format in which we store User Profiles in
137
export interface ProfileDetails {
138
// when was the last time the user updated their profile information or has been nudged to do so.
139
lastUpdatedDetailsNudge?: string;
140
// when was the last time the user has accepted our privacy policy
141
acceptedPrivacyPolicyDate?: string;
142
// the user's company name
143
companyName?: string;
144
// the user's email
145
emailAddress?: string;
146
// type of role user has in their job
147
jobRole?: string;
148
// freeform entry for job role user works in (when jobRole is "other")
149
jobRoleOther?: string;
150
// Reasons user is exploring Gitpod when they signed up
151
explorationReasons?: string[];
152
// what user hopes to accomplish when they signed up
153
signupGoals?: string[];
154
// freeform entry for signup goals (when signupGoals is "other")
155
signupGoalsOther?: string;
156
// Set after a user completes the onboarding flow
157
onboardedTimestamp?: string;
158
// Onboarding question about a user's company size
159
companySize?: string;
160
// key-value pairs of dialogs in the UI which should only appear once. The value usually is a timestamp of the last dismissal
161
coachmarksDismissals?: { [key: string]: string };
162
}
163
164
export interface EmailNotificationSettings {
165
allowsChangelogMail?: boolean;
166
allowsDevXMail?: boolean;
167
allowsOnboardingMail?: boolean;
168
}
169
170
export type IDESettings = {
171
settingVersion?: string;
172
defaultIde?: string;
173
useLatestVersion?: boolean;
174
preferToolbox?: boolean;
175
// DEPRECATED: Use defaultIde after `settingVersion: 2.0`, no more specialify desktop or browser.
176
useDesktopIde?: boolean;
177
// DEPRECATED: Same with useDesktopIde.
178
defaultDesktopIde?: string;
179
};
180
181
export interface WorkspaceClasses {
182
regular?: string;
183
/**
184
* @deprecated see Project.settings.prebuilds.workspaceClass
185
*/
186
prebuild?: string;
187
}
188
189
export interface UserPlatform {
190
uid: string;
191
userAgent: string;
192
browser: string;
193
os: string;
194
lastUsed: string;
195
firstUsed: string;
196
/**
197
* Since when does the user have the browser extension installe don this device.
198
*/
199
browserExtensionInstalledSince?: string;
200
201
/**
202
* Since when does the user not have the browser extension installed anymore (but previously had).
203
*/
204
browserExtensionUninstalledSince?: string;
205
}
206
207
export interface UserFeatureSettings {
208
/**
209
* Permanent feature flags are added to each and every workspace instance
210
* this user starts.
211
*/
212
permanentWSFeatureFlags?: NamedWorkspaceFeatureFlag[];
213
}
214
215
export type BillingTier = "paid" | "free";
216
217
/**
218
* The values of this type MUST MATCH enum values in WorkspaceFeatureFlag from ws-manager/client/core_pb.d.ts
219
* If they don't we'll break things during workspace startup.
220
*/
221
export const WorkspaceFeatureFlags = {
222
full_workspace_backup: undefined,
223
workspace_class_limiting: undefined,
224
workspace_connection_limiting: undefined,
225
workspace_psi: undefined,
226
ssh_ca: undefined,
227
};
228
export type NamedWorkspaceFeatureFlag = keyof typeof WorkspaceFeatureFlags;
229
export namespace NamedWorkspaceFeatureFlag {
230
export const WORKSPACE_PERSISTED_FEATTURE_FLAGS: NamedWorkspaceFeatureFlag[] = ["full_workspace_backup"];
231
export function isWorkspacePersisted(ff: NamedWorkspaceFeatureFlag): boolean {
232
return WORKSPACE_PERSISTED_FEATTURE_FLAGS.includes(ff);
233
}
234
}
235
236
export type EnvVar = UserEnvVar | ProjectEnvVarWithValue | OrgEnvVarWithValue | EnvVarWithValue;
237
238
export interface EnvVarWithValue {
239
name: string;
240
value: string;
241
}
242
243
export interface ProjectEnvVarWithValue extends EnvVarWithValue {
244
id?: string;
245
/** If a project-scoped env var is "censored", it is only visible in Prebuilds */
246
censored: boolean;
247
}
248
249
export interface ProjectEnvVar extends Omit<ProjectEnvVarWithValue, "value"> {
250
id: string;
251
projectId: string;
252
}
253
254
export interface OrgEnvVarWithValue extends EnvVarWithValue {
255
id?: string;
256
}
257
258
export interface OrgEnvVar extends Omit<OrgEnvVarWithValue, "value"> {
259
id: string;
260
orgId: string;
261
}
262
263
export interface UserEnvVarValue extends EnvVarWithValue {
264
id?: string;
265
repositoryPattern: string; // DEPRECATED: Use ProjectEnvVar instead of repositoryPattern - https://github.com/gitpod-com/gitpod/issues/5322
266
}
267
export interface UserEnvVar extends UserEnvVarValue {
268
id: string;
269
userId: string;
270
deleted?: boolean;
271
}
272
273
export namespace EnvVar {
274
export const GITPOD_IMAGE_AUTH_ENV_VAR_NAME = "GITPOD_IMAGE_AUTH";
275
/**
276
* - GITPOD_IMAGE_AUTH is documented https://www.gitpod.io/docs/configure/workspaces/workspace-image#use-a-private-docker-image
277
*/
278
export const WhiteListFromReserved = [GITPOD_IMAGE_AUTH_ENV_VAR_NAME];
279
280
export function is(data: any): data is EnvVar {
281
return data.hasOwnProperty("name") && data.hasOwnProperty("value");
282
}
283
284
/**
285
* Extracts the "host:credentials" pairs from the GITPOD_IMAGE_AUTH environment variable.
286
* @param envVars
287
* @returns A map of host to credentials
288
*/
289
export function getGitpodImageAuth(envVars: EnvVarWithValue[]): Map<string, string> {
290
const res = new Map<string, string>();
291
const imageAuth = envVars.find((e) => e.name === EnvVar.GITPOD_IMAGE_AUTH_ENV_VAR_NAME);
292
if (!imageAuth) {
293
return res;
294
}
295
296
const parse = (parts: string[]): { host: string; auth: string } | undefined => {
297
if (parts.some((e) => e === "")) {
298
return undefined;
299
}
300
if (parts.length === 2) {
301
return { host: parts[0], auth: parts[1] };
302
} else if (parts.length === 3) {
303
return { host: `${parts[0]}:${parts[1]}`, auth: parts[2] };
304
}
305
return undefined;
306
};
307
308
(imageAuth.value || "")
309
.split(",")
310
.map((e) => e.split(":").map((part) => part.trim()))
311
.forEach((parts) => {
312
const parsed = parse(parts);
313
if (parsed) {
314
res.set(parsed.host, parsed.auth);
315
}
316
});
317
318
return res;
319
}
320
}
321
322
export namespace UserEnvVar {
323
export const DELIMITER = "/";
324
export const WILDCARD_ASTERISK = "*";
325
// This wildcard is only allowed on the last segment, and matches arbitrary segments to the right
326
export const WILDCARD_DOUBLE_ASTERISK = "**";
327
const WILDCARD_SHARP = "#"; // TODO(gpl) Where does this come from? Bc we have/had patterns as part of URLs somewhere, maybe...?
328
const MIN_PATTERN_SEGMENTS = 2;
329
330
function isWildcard(c: string): boolean {
331
return c === WILDCARD_ASTERISK || c === WILDCARD_SHARP;
332
}
333
334
export function is(data: any): data is UserEnvVar {
335
return (
336
EnvVar.is(data) &&
337
data.hasOwnProperty("id") &&
338
data.hasOwnProperty("userId") &&
339
data.hasOwnProperty("repositoryPattern")
340
);
341
}
342
343
/**
344
* @param variable
345
* @returns Either a string containing an error message or undefined.
346
*/
347
export function validate(variable: UserEnvVarValue): string | undefined {
348
const name = variable.name;
349
const pattern = variable.repositoryPattern;
350
if (!EnvVar.WhiteListFromReserved.includes(name) && name.startsWith("GITPOD_")) {
351
return "Name with prefix 'GITPOD_' is reserved.";
352
}
353
if (name.trim() === "") {
354
return "Name must not be empty.";
355
}
356
if (name.length > 255) {
357
return "Name too long. Maximum name length is 255 characters.";
358
}
359
if (!/^[a-zA-Z_]+[a-zA-Z0-9_]*$/.test(name)) {
360
return "Name must match /^[a-zA-Z_]+[a-zA-Z0-9_]*$/.";
361
}
362
if (variable.value.trim() === "") {
363
return "Value must not be empty.";
364
}
365
if (variable.value.length > 32767) {
366
return "Value too long. Maximum value length is 32767 characters.";
367
}
368
if (pattern.trim() === "") {
369
return "Scope must not be empty.";
370
}
371
const split = splitRepositoryPattern(pattern);
372
if (split.length < MIN_PATTERN_SEGMENTS) {
373
return "A scope must use the form 'organization/repo'.";
374
}
375
for (const name of split) {
376
if (name !== "*") {
377
if (!/^[a-zA-Z0-9_\-.\*]+$/.test(name)) {
378
return "Invalid scope segment. Only ASCII characters, numbers, -, _, . or * are allowed.";
379
}
380
}
381
}
382
return undefined;
383
}
384
385
export function normalizeRepoPattern(pattern: string) {
386
return pattern.toLocaleLowerCase();
387
}
388
389
function splitRepositoryPattern(pattern: string): string[] {
390
return pattern.split(DELIMITER);
391
}
392
393
function joinRepositoryPattern(...parts: string[]): string {
394
return parts.join(DELIMITER);
395
}
396
397
/**
398
* Matches the given EnvVar pattern against the fully qualified name of the repository:
399
* - GitHub: "repo/owner"
400
* - GitLab: "some/nested/repo" (up to MAX_PATTERN_SEGMENTS deep)
401
* @param pattern
402
* @param repoFqn
403
* @returns True if the pattern matches that fully qualified name
404
*/
405
export function matchEnvVarPattern(pattern: string, repoFqn: string): boolean {
406
const fqnSegments = splitRepositoryPattern(repoFqn);
407
const patternSegments = splitRepositoryPattern(pattern);
408
if (patternSegments.length < MIN_PATTERN_SEGMENTS) {
409
// Technically not a problem, but we should not allow this for safety reasons.
410
// And because we hisotrically never allowed this (GitHub would always require at least "*/*") we are just keeping old constraints.
411
// Note: Most importantly this guards against arbitrary wildcard matches
412
return false;
413
}
414
415
function isWildcardMatch(patternSegment: string, isLastSegment: boolean): boolean {
416
if (isWildcard(patternSegment)) {
417
return true;
418
}
419
return isLastSegment && patternSegment === WILDCARD_DOUBLE_ASTERISK;
420
}
421
let i = 0;
422
for (; i < patternSegments.length; i++) {
423
if (i >= fqnSegments.length) {
424
return false;
425
}
426
const fqnSegment = fqnSegments[i];
427
const patternSegment = patternSegments[i];
428
const isLastSegment = patternSegments.length === i + 1;
429
if (!isWildcardMatch(patternSegment, isLastSegment) && patternSegment !== fqnSegment.toLocaleLowerCase()) {
430
return false;
431
}
432
}
433
if (fqnSegments.length > i) {
434
// Special case: "**" as last segment matches arbitrary # of segments to the right
435
if (patternSegments[i - 1] === WILDCARD_DOUBLE_ASTERISK) {
436
return true;
437
}
438
return false;
439
}
440
return true;
441
}
442
443
// Matches the following patterns for "some/nested/repo", ordered by highest score:
444
// - exact: some/nested/repo ()
445
// - partial:
446
// - some/nested/*
447
// - some/*
448
// - generic:
449
// - */*/*
450
// - */*
451
// Does NOT match:
452
// - */repo (number of parts does not match)
453
// cmp. test cases in env-var-service.spec.ts
454
export function filter<T extends UserEnvVarValue>(vars: T[], owner: string, repo: string): T[] {
455
const matchedEnvVars = vars.filter((e) =>
456
matchEnvVarPattern(e.repositoryPattern, joinRepositoryPattern(owner, repo)),
457
);
458
const resmap = new Map<string, T[]>();
459
matchedEnvVars.forEach((e) => {
460
const l = resmap.get(e.name) || [];
461
l.push(e);
462
resmap.set(e.name, l);
463
});
464
465
// In cases of multiple matches per env var: find the best match
466
// Best match is the most specific pattern, where the left-most segment is most significant
467
function scoreMatchedEnvVar(e: T): number {
468
function valueSegment(segment: string): number {
469
switch (segment) {
470
case WILDCARD_ASTERISK:
471
return 2;
472
case WILDCARD_SHARP:
473
return 2;
474
case WILDCARD_DOUBLE_ASTERISK:
475
return 1;
476
default:
477
return 3;
478
}
479
}
480
const patternSegments = splitRepositoryPattern(e.repositoryPattern);
481
let result = 0;
482
for (const segment of patternSegments) {
483
// We can assume the pattern matches from context, so we just need to check whether it's a wildcard or not
484
const val = valueSegment(segment);
485
result = result * 10 + val;
486
}
487
return result;
488
}
489
const result = [];
490
for (const name of resmap.keys()) {
491
const candidates = resmap.get(name);
492
if (!candidates) {
493
// not sure how this can happen, but so be it
494
continue;
495
}
496
497
if (candidates.length == 1) {
498
result.push(candidates[0]);
499
continue;
500
}
501
502
let bestCandidate = candidates[0];
503
let bestScore = scoreMatchedEnvVar(bestCandidate);
504
for (const e of candidates.slice(1)) {
505
const score = scoreMatchedEnvVar(e);
506
if (score > bestScore) {
507
bestScore = score;
508
bestCandidate = e;
509
}
510
}
511
result.push(bestCandidate!);
512
}
513
514
return result;
515
}
516
}
517
518
export interface SSHPublicKeyValue {
519
name: string;
520
key: string;
521
}
522
export interface UserSSHPublicKey extends SSHPublicKeyValue {
523
id: string;
524
key: string;
525
userId: string;
526
fingerprint: string;
527
creationTime: string;
528
lastUsedTime?: string;
529
}
530
531
export type UserSSHPublicKeyValue = Omit<UserSSHPublicKey, "userId">;
532
533
export namespace SSHPublicKeyValue {
534
export function validate(value: SSHPublicKeyValue): string | undefined {
535
if (value.name.length === 0) {
536
return "Title must not be empty.";
537
}
538
if (value.name.length > 255) {
539
return "Title too long. Maximum value length is 255 characters.";
540
}
541
if (value.key.length === 0) {
542
return "Key must not be empty.";
543
}
544
try {
545
getData(value);
546
} catch (e) {
547
return "Key is invalid. You must supply a key in OpenSSH public key format.";
548
}
549
return;
550
}
551
552
export function getData(value: SSHPublicKeyValue) {
553
// Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', '[email protected]', or '[email protected]'.
554
const regex =
555
/^(?<type>ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh\.com|sk-ssh-ed25519@openssh\.com) (?<key>.*?)( (?<email>.*?))?$/;
556
const resultGroup = regex.exec(value.key.trim());
557
if (!resultGroup) {
558
throw new Error("Key is invalid.");
559
}
560
return {
561
type: resultGroup.groups?.["type"] as string,
562
key: resultGroup.groups?.["key"] as string,
563
email: resultGroup.groups?.["email"] || undefined,
564
};
565
}
566
567
export function getFingerprint(value: SSHPublicKeyValue) {
568
const data = getData(value);
569
const buf = Buffer.from(data.key, "base64");
570
// gitlab style
571
// const hash = createHash("md5").update(buf).digest("hex");
572
// github style
573
const hash = createHash("sha256").update(buf).digest("base64");
574
return hash;
575
}
576
577
export const MAXIMUM_KEY_LENGTH = 5;
578
}
579
580
export interface GitpodToken {
581
/** Hash value (SHA256) of the token (primary key). */
582
tokenHash: string;
583
584
/** Human readable name of the token */
585
name?: string;
586
587
/** Token kind */
588
type: GitpodTokenType;
589
590
/** The user the token belongs to. */
591
userId: string;
592
593
/** Scopes (e.g. limition to read-only) */
594
scopes: string[];
595
596
/** Created timestamp */
597
created: string;
598
}
599
600
export enum GitpodTokenType {
601
API_AUTH_TOKEN = 0,
602
MACHINE_AUTH_TOKEN = 1,
603
}
604
605
export interface OneTimeSecret {
606
id: string;
607
608
value: string;
609
610
expirationTime: string;
611
612
deleted: boolean;
613
}
614
615
export interface WorkspaceInstanceUser {
616
name?: string;
617
avatarUrl?: string;
618
instanceId: string;
619
userId: string;
620
lastSeen: string;
621
}
622
623
export interface Identity {
624
authProviderId: string;
625
authId: string;
626
authName: string;
627
primaryEmail?: string;
628
/** This is a flag that triggers the HARD DELETION of this entity */
629
deleted?: boolean;
630
// The last time this entry was touched during a signin. It's only set for SSO identities.
631
lastSigninTime?: string;
632
633
// @deprecated as no longer in use since '19
634
readonly?: boolean;
635
}
636
637
export type IdentityLookup = Pick<Identity, "authProviderId" | "authId">;
638
639
export namespace Identity {
640
export function is(data: any): data is Identity {
641
return (
642
data.hasOwnProperty("authProviderId") && data.hasOwnProperty("authId") && data.hasOwnProperty("authName")
643
);
644
}
645
export function equals(id1: IdentityLookup, id2: IdentityLookup) {
646
return id1.authProviderId === id2.authProviderId && id1.authId === id2.authId;
647
}
648
}
649
650
export interface Token {
651
value: string;
652
scopes: string[];
653
updateDate?: string;
654
expiryDate?: string;
655
reservedUntilDate?: string;
656
idToken?: string;
657
refreshToken?: string;
658
username?: string;
659
}
660
export interface TokenEntry {
661
uid: string;
662
authProviderId: string;
663
authId: string;
664
token: Token;
665
expiryDate?: string;
666
reservedUntilDate?: string;
667
refreshable?: boolean;
668
}
669
670
export interface EmailDomainFilterEntry {
671
domain: string;
672
negative: boolean;
673
}
674
675
export type AppInstallationPlatform = "github";
676
export type AppInstallationState = "claimed.user" | "claimed.platform" | "installed" | "uninstalled";
677
export interface AppInstallation {
678
platform: AppInstallationPlatform;
679
installationID: string;
680
ownerUserID?: string;
681
platformUserID?: string;
682
state: AppInstallationState;
683
creationTime: string;
684
lastUpdateTime: string;
685
}
686
687
export interface PendingGithubEvent {
688
id: string;
689
githubUserId: string;
690
creationDate: Date;
691
type: string;
692
event: string;
693
deleted: boolean;
694
}
695
696
export interface Snapshot {
697
id: string;
698
creationTime: string;
699
availableTime?: string;
700
originalWorkspaceId: string;
701
bucketId: string;
702
state: SnapshotState;
703
message?: string;
704
}
705
706
export type SnapshotState = "pending" | "available" | "error";
707
708
export interface Workspace {
709
id: string;
710
creationTime: string;
711
organizationId: string;
712
contextURL: string;
713
description: string;
714
ownerId: string;
715
projectId?: string;
716
context: WorkspaceContext;
717
config: WorkspaceConfig;
718
719
/**
720
* The source where to get the workspace base image from. This source is resolved
721
* during workspace creation. Once a base image has been built the information in here
722
* is superseded by baseImageNameResolved.
723
*/
724
imageSource?: WorkspaceImageSource;
725
726
/**
727
* The resolved, fix name of the workspace image. We only use this
728
* to access the logs during an image build.
729
*/
730
imageNameResolved?: string;
731
732
/**
733
* The resolved/built fixed named of the base image. This field is only set if the workspace
734
* already has its base image built.
735
*/
736
baseImageNameResolved?: string;
737
738
shareable?: boolean;
739
pinned?: boolean;
740
741
// workspace is hard-deleted on the database and about to be collected by periodic deleter
742
readonly deleted?: boolean;
743
744
/**
745
* Mark as deleted (user-facing). The actual deletion of the workspace content is executed
746
* with a (configurable) delay
747
*/
748
softDeleted?: WorkspaceSoftDeletion;
749
750
/**
751
* Marks the time when the workspace was marked as softDeleted. The actual deletion of the
752
* workspace content happens after a configurable period
753
*/
754
softDeletedTime?: string;
755
756
/**
757
* Marks the time when the workspace content has been deleted.
758
*/
759
contentDeletedTime?: string;
760
761
/**
762
* The time when the workspace is eligible for soft deletion. This is the time when the workspace
763
* is marked as softDeleted earliest.
764
*/
765
deletionEligibilityTime?: string;
766
767
type: WorkspaceType;
768
769
basedOnPrebuildId?: string;
770
771
basedOnSnapshotId?: string;
772
}
773
774
export type WorkspaceSoftDeletion = "user" | "gc";
775
776
export type WorkspaceType = "regular" | "prebuild" | "imagebuild";
777
778
export namespace Workspace {
779
export function getPullRequestNumber(ws: Workspace): number | undefined {
780
if (PullRequestContext.is(ws.context)) {
781
return ws.context.nr;
782
}
783
return undefined;
784
}
785
786
export function getIssueNumber(ws: Workspace): number | undefined {
787
if (IssueContext.is(ws.context)) {
788
return ws.context.nr;
789
}
790
return undefined;
791
}
792
793
export function getBranchName(ws: Workspace): string | undefined {
794
if (CommitContext.is(ws.context)) {
795
return ws.context.ref;
796
}
797
return undefined;
798
}
799
800
export function getCommit(ws: Workspace): string | undefined {
801
if (CommitContext.is(ws.context)) {
802
return ws.context.revision && ws.context.revision.substr(0, 8);
803
}
804
return undefined;
805
}
806
807
const NAME_PREFIX = "named:";
808
export function fromWorkspaceName(name?: Workspace["description"]): string | undefined {
809
if (name?.startsWith(NAME_PREFIX)) {
810
return name.slice(NAME_PREFIX.length);
811
}
812
return undefined;
813
}
814
export function toWorkspaceName(name?: Workspace["description"]): string {
815
if (!name || name?.trim().length === 0) {
816
return "no-name";
817
}
818
return `${NAME_PREFIX}${name}`;
819
}
820
}
821
822
export interface GuessGitTokenScopesParams {
823
host: string;
824
repoUrl: string;
825
gitCommand: string;
826
}
827
828
export interface GuessedGitTokenScopes {
829
message?: string;
830
scopes?: string[];
831
}
832
833
export interface VSCodeConfig {
834
extensions?: string[];
835
}
836
837
export interface JetBrainsConfig {
838
intellij?: JetBrainsProductConfig;
839
goland?: JetBrainsProductConfig;
840
pycharm?: JetBrainsProductConfig;
841
phpstorm?: JetBrainsProductConfig;
842
rubymine?: JetBrainsProductConfig;
843
webstorm?: JetBrainsProductConfig;
844
rider?: JetBrainsProductConfig;
845
clion?: JetBrainsProductConfig;
846
rustrover?: JetBrainsProductConfig;
847
}
848
export interface JetBrainsProductConfig {
849
prebuilds?: JetBrainsPrebuilds;
850
vmoptions?: string;
851
}
852
export interface JetBrainsPrebuilds {
853
version?: "stable" | "latest" | "both";
854
}
855
856
export interface RepositoryCloneInformation {
857
url: string;
858
checkoutLocation?: string;
859
}
860
861
export interface CoreDumpConfig {
862
enabled?: boolean;
863
softLimit?: number;
864
hardLimit?: number;
865
}
866
867
export interface WorkspaceConfig {
868
mainConfiguration?: string;
869
additionalRepositories?: RepositoryCloneInformation[];
870
image?: ImageConfig;
871
ports?: PortConfig[];
872
tasks?: TaskConfig[];
873
checkoutLocation?: string;
874
workspaceLocation?: string;
875
gitConfig?: { [config: string]: string };
876
github?: GithubAppConfig;
877
vscode?: VSCodeConfig;
878
jetbrains?: JetBrainsConfig;
879
coreDump?: CoreDumpConfig;
880
ideCredentials?: string;
881
env?: { [env: string]: any };
882
883
/** deprecated. Enabled by default **/
884
experimentalNetwork?: boolean;
885
886
/**
887
* Where the config object originates from.
888
*
889
* repo - from the repository
890
* derived - computed based on analyzing the repository
891
* additional-content - config comes from additional content, usually provided through the project's configuration
892
* default - our static catch-all default config
893
*/
894
_origin?: "repo" | "derived" | "additional-content" | "default";
895
896
/**
897
* Set of automatically infered feature flags. That's not something the user can set, but
898
* that is set by gitpod at workspace creation time.
899
*/
900
_featureFlags?: NamedWorkspaceFeatureFlag[];
901
}
902
903
export interface GithubAppConfig {
904
prebuilds?: GithubAppPrebuildConfig;
905
}
906
export interface GithubAppPrebuildConfig {
907
master?: boolean;
908
branches?: boolean;
909
pullRequests?: boolean;
910
pullRequestsFromForks?: boolean;
911
addCheck?: boolean | "prevent-merge-on-error";
912
addBadge?: boolean;
913
addLabel?: boolean | string;
914
addComment?: boolean;
915
}
916
export namespace GithubAppPrebuildConfig {
917
export function is(obj: boolean | GithubAppPrebuildConfig): obj is GithubAppPrebuildConfig {
918
return !(typeof obj === "boolean");
919
}
920
}
921
922
export type WorkspaceImageSource = WorkspaceImageSourceDocker | WorkspaceImageSourceReference;
923
export interface WorkspaceImageSourceDocker {
924
dockerFilePath: string;
925
dockerFileHash: string;
926
dockerFileSource?: Commit;
927
}
928
export namespace WorkspaceImageSourceDocker {
929
export function is(obj: object): obj is WorkspaceImageSourceDocker {
930
return "dockerFileHash" in obj && "dockerFilePath" in obj;
931
}
932
}
933
export interface WorkspaceImageSourceReference {
934
/** The resolved, fix base image reference */
935
baseImageResolved: string;
936
}
937
export namespace WorkspaceImageSourceReference {
938
export function is(obj: object): obj is WorkspaceImageSourceReference {
939
return "baseImageResolved" in obj;
940
}
941
}
942
943
export type PrebuiltWorkspaceState =
944
// the prebuild is queued and may start at anytime
945
| "queued"
946
// the workspace prebuild is currently running (i.e. there's a workspace pod deployed)
947
| "building"
948
// the prebuild was aborted
949
| "aborted"
950
// the prebuild timed out
951
| "timeout"
952
// the prebuild has finished (even if a headless task failed) and a snapshot is available
953
| "available"
954
// the prebuild (headless workspace) failed due to some system error
955
| "failed";
956
957
export interface PrebuiltWorkspace {
958
id: string;
959
cloneURL: string;
960
branch?: string;
961
projectId?: string;
962
commit: string;
963
buildWorkspaceId: string;
964
creationTime: string;
965
state: PrebuiltWorkspaceState;
966
statusVersion: number;
967
error?: string;
968
snapshot?: string;
969
}
970
971
export type PrebuiltWorkspaceWithWorkspace = PrebuiltWorkspace & { workspace: Workspace };
972
973
export namespace PrebuiltWorkspace {
974
export function isDone(pws: PrebuiltWorkspace) {
975
return (
976
pws.state === "available" || pws.state === "timeout" || pws.state === "aborted" || pws.state === "failed"
977
);
978
}
979
980
export function isAvailable(pws: PrebuiltWorkspace) {
981
return pws.state === "available" && !!pws.snapshot;
982
}
983
984
export function buildDidSucceed(pws: PrebuiltWorkspace) {
985
return pws.state === "available" && !pws.error;
986
}
987
}
988
989
export interface PrebuiltWorkspaceUpdatable {
990
id: string;
991
prebuiltWorkspaceId: string;
992
owner: string;
993
repo: string;
994
isResolved: boolean;
995
installationId: string;
996
/**
997
* the commitSHA of the commit that triggered the prebuild
998
*/
999
commitSHA?: string;
1000
issue?: string;
1001
contextUrl?: string;
1002
}
1003
1004
export type PortOnOpen = "open-browser" | "open-preview" | "notify" | "ignore" | "ignore-completely";
1005
1006
export interface PortConfig {
1007
port: number;
1008
onOpen?: PortOnOpen;
1009
visibility?: PortVisibility;
1010
protocol?: PortProtocol;
1011
description?: string;
1012
name?: string;
1013
}
1014
export namespace PortConfig {
1015
export function is(config: any): config is PortConfig {
1016
return config && "port" in config && typeof config.port === "number";
1017
}
1018
}
1019
1020
export interface PortRangeConfig {
1021
port: string;
1022
onOpen?: PortOnOpen;
1023
}
1024
export namespace PortRangeConfig {
1025
export function is(config: any): config is PortRangeConfig {
1026
return config && "port" in config && (typeof config.port === "string" || config.port instanceof String);
1027
}
1028
}
1029
1030
export interface TaskConfig {
1031
name?: string;
1032
before?: string;
1033
init?: string;
1034
prebuild?: string;
1035
command?: string;
1036
env?: { [env: string]: any };
1037
openIn?: "bottom" | "main" | "left" | "right";
1038
openMode?: "split-top" | "split-left" | "split-right" | "split-bottom" | "tab-before" | "tab-after";
1039
}
1040
1041
export namespace TaskConfig {
1042
export function is(config: any): config is TaskConfig {
1043
return config && ("command" in config || "init" in config || "before" in config);
1044
}
1045
}
1046
1047
export namespace WorkspaceImageBuild {
1048
export type Phase = "BaseImage" | "GitpodLayer" | "Error" | "Done";
1049
export interface StateInfo {
1050
phase: Phase;
1051
currentStep?: number;
1052
maxSteps?: number;
1053
}
1054
export interface LogContent {
1055
data: number[]; // encode with "Array.from(UInt8Array)"", decode with "new UInt8Array(data)"
1056
}
1057
export type LogCallback = (info: StateInfo, content: LogContent | undefined) => void;
1058
export namespace LogLine {
1059
export const DELIMITER = "\r\n";
1060
export const DELIMITER_REGEX = /\r?\n/;
1061
}
1062
}
1063
1064
export type ImageConfig = ImageConfigString | ImageConfigFile;
1065
export type ImageConfigString = string;
1066
export namespace ImageConfigString {
1067
export function is(config: ImageConfig | undefined): config is ImageConfigString {
1068
return typeof config === "string";
1069
}
1070
}
1071
export interface ImageConfigFile {
1072
// Path to the Dockerfile relative to repository root
1073
file: string;
1074
// Path to the docker build context relative to repository root
1075
context?: string;
1076
}
1077
export namespace ImageConfigFile {
1078
export function is(config: ImageConfig | undefined): config is ImageConfigFile {
1079
return typeof config === "object" && "file" in config;
1080
}
1081
}
1082
export interface ExternalImageConfigFile extends ImageConfigFile {
1083
externalSource: Commit;
1084
}
1085
export namespace ExternalImageConfigFile {
1086
export function is(config: any | undefined): config is ExternalImageConfigFile {
1087
return typeof config === "object" && "file" in config && "externalSource" in config;
1088
}
1089
}
1090
1091
export interface WorkspaceContext {
1092
title: string;
1093
ref?: string;
1094
/** This contains the URL portion of the contextURL (which might contain other modifiers as well). It's optional because it's not set for older workspaces. */
1095
normalizedContextURL?: string;
1096
forceCreateNewWorkspace?: boolean;
1097
forceImageBuild?: boolean;
1098
// The context can have non-blocking warnings that should be displayed to the user.
1099
warnings?: string[];
1100
}
1101
1102
export namespace WorkspaceContext {
1103
export function is(context: any): context is WorkspaceContext {
1104
return context && "title" in context;
1105
}
1106
}
1107
1108
export interface WithSnapshot {
1109
snapshotBucketId: string;
1110
}
1111
export namespace WithSnapshot {
1112
export function is(context: any): context is WithSnapshot {
1113
return context && "snapshotBucketId" in context;
1114
}
1115
}
1116
1117
export interface WithPrebuild extends WithSnapshot {
1118
prebuildWorkspaceId: string;
1119
wasPrebuilt: true;
1120
}
1121
export namespace WithPrebuild {
1122
export function is(context: any): context is WithPrebuild {
1123
return context && WithSnapshot.is(context) && "prebuildWorkspaceId" in context && "wasPrebuilt" in context;
1124
}
1125
}
1126
1127
/**
1128
* WithDefaultConfig contexts disable the download of the gitpod.yml from the repository
1129
* and fall back to the built-in configuration.
1130
*/
1131
export interface WithDefaultConfig {
1132
withDefaultConfig: true;
1133
}
1134
1135
export namespace WithDefaultConfig {
1136
export function is(context: any): context is WithDefaultConfig {
1137
return context && "withDefaultConfig" in context && context.withDefaultConfig;
1138
}
1139
1140
export function mark(ctx: WorkspaceContext): WorkspaceContext & WithDefaultConfig {
1141
return {
1142
...ctx,
1143
withDefaultConfig: true,
1144
};
1145
}
1146
}
1147
1148
export interface SnapshotContext extends WorkspaceContext, WithSnapshot {
1149
snapshotId: string;
1150
}
1151
1152
export namespace SnapshotContext {
1153
export function is(context: any): context is SnapshotContext {
1154
return context && WithSnapshot.is(context) && "snapshotId" in context;
1155
}
1156
}
1157
1158
export interface WithCommitHistory {
1159
commitHistory?: string[];
1160
additionalRepositoryCommitHistories?: {
1161
cloneUrl: string;
1162
commitHistory: string[];
1163
}[];
1164
}
1165
1166
export interface StartPrebuildContext extends WorkspaceContext, WithCommitHistory {
1167
actual: WorkspaceContext;
1168
project: Project;
1169
branch?: string;
1170
}
1171
1172
export namespace StartPrebuildContext {
1173
export function is(context: any): context is StartPrebuildContext {
1174
return context && "actual" in context;
1175
}
1176
}
1177
1178
export interface PrebuiltWorkspaceContext extends WorkspaceContext {
1179
originalContext: WorkspaceContext;
1180
prebuiltWorkspace: PrebuiltWorkspace;
1181
snapshotBucketId?: string;
1182
}
1183
1184
export namespace PrebuiltWorkspaceContext {
1185
export function is(context: any): context is PrebuiltWorkspaceContext {
1186
return context && "originalContext" in context && "prebuiltWorkspace" in context;
1187
}
1188
}
1189
1190
export interface WithReferrerContext extends WorkspaceContext {
1191
referrer: string;
1192
referrerIde?: string;
1193
}
1194
1195
export namespace WithReferrerContext {
1196
export function is(context: any): context is WithReferrerContext {
1197
return context && "referrer" in context;
1198
}
1199
}
1200
1201
export interface WithEnvvarsContext extends WorkspaceContext {
1202
envvars: EnvVarWithValue[];
1203
}
1204
1205
export namespace WithEnvvarsContext {
1206
export function is(context: any): context is WithEnvvarsContext {
1207
return context && "envvars" in context;
1208
}
1209
}
1210
1211
export type RefType = "branch" | "tag" | "revision";
1212
export namespace RefType {
1213
export const getRefType = (commit: Commit): RefType => {
1214
if (!commit.ref) {
1215
return "revision";
1216
}
1217
// This fallback is meant to handle the cases where (for historic reasons) ref is present but refType is missing
1218
return commit.refType || "branch";
1219
};
1220
}
1221
1222
export interface Commit {
1223
repository: Repository;
1224
revision: string;
1225
1226
// Might contain either a branch or a tag (determined by refType)
1227
ref?: string;
1228
1229
// refType is only set if ref is present (and not for old workspaces, before this feature was added)
1230
refType?: RefType;
1231
}
1232
1233
export interface AdditionalContentContext extends WorkspaceContext {
1234
/**
1235
* utf-8 encoded contents that will be copied on top of the workspace's filesystem
1236
*/
1237
additionalFiles: { [filePath: string]: string };
1238
}
1239
1240
export namespace AdditionalContentContext {
1241
export function is(ctx: any): ctx is AdditionalContentContext {
1242
return "additionalFiles" in ctx;
1243
}
1244
1245
export function hasDockerConfig(ctx: any, config: WorkspaceConfig): boolean {
1246
return is(ctx) && ImageConfigFile.is(config.image) && !!ctx.additionalFiles[config.image.file];
1247
}
1248
}
1249
1250
export interface OpenPrebuildContext extends WorkspaceContext {
1251
openPrebuildID: string;
1252
}
1253
1254
export namespace OpenPrebuildContext {
1255
export function is(ctx: any): ctx is OpenPrebuildContext {
1256
return "openPrebuildID" in ctx;
1257
}
1258
}
1259
1260
export interface CommitContext extends WorkspaceContext, GitCheckoutInfo {
1261
/** @deprecated Moved to .repository.cloneUrl, left here for backwards-compatibility for old workspace contextes in the DB */
1262
cloneUrl?: string;
1263
1264
/**
1265
* The clone and checkout information for additional repositories in case of multi-repo projects.
1266
*/
1267
additionalRepositoryCheckoutInfo?: GitCheckoutInfo[];
1268
}
1269
1270
export namespace CommitContext {
1271
/**
1272
* Creates a hash for all the commits of the CommitContext and all sub-repo commit infos.
1273
* The hash is max 255 chars long.
1274
* @param commitContext
1275
* @returns hash for commitcontext
1276
*/
1277
export function computeHash(commitContext: CommitContext): string {
1278
// for single commits we use the revision to be backward compatible.
1279
if (
1280
!commitContext.additionalRepositoryCheckoutInfo ||
1281
commitContext.additionalRepositoryCheckoutInfo.length === 0
1282
) {
1283
return commitContext.revision;
1284
}
1285
const hasher = createHash("sha256");
1286
hasher.update(commitContext.revision);
1287
for (const info of commitContext.additionalRepositoryCheckoutInfo) {
1288
hasher.update(info.revision);
1289
}
1290
return hasher.digest("hex");
1291
}
1292
1293
export function isDefaultBranch(commitContext: CommitContext): boolean {
1294
return commitContext.ref === commitContext.repository.defaultBranch;
1295
}
1296
}
1297
1298
export interface GitCheckoutInfo extends Commit {
1299
checkoutLocation?: string;
1300
upstreamRemoteURI?: string;
1301
localBranch?: string;
1302
}
1303
1304
export namespace CommitContext {
1305
export function is(commit: any): commit is CommitContext {
1306
return WorkspaceContext.is(commit) && "repository" in commit && "revision" in commit;
1307
}
1308
}
1309
1310
export interface PullRequestContext extends CommitContext {
1311
nr: number;
1312
ref: string;
1313
base: {
1314
repository: Repository;
1315
ref: string;
1316
};
1317
}
1318
1319
export namespace PullRequestContext {
1320
export function is(ctx: any): ctx is PullRequestContext {
1321
return CommitContext.is(ctx) && "nr" in ctx && "ref" in ctx && "base" in ctx;
1322
}
1323
}
1324
1325
export interface IssueContext extends CommitContext {
1326
nr: number;
1327
ref: string;
1328
localBranch: string;
1329
}
1330
1331
export namespace IssueContext {
1332
export function is(ctx: any): ctx is IssueContext {
1333
return CommitContext.is(ctx) && "nr" in ctx && "ref" in ctx && "localBranch" in ctx;
1334
}
1335
}
1336
1337
export interface NavigatorContext extends CommitContext {
1338
path: string;
1339
isFile: boolean;
1340
}
1341
1342
export namespace NavigatorContext {
1343
export function is(ctx: any): ctx is NavigatorContext {
1344
return CommitContext.is(ctx) && "path" in ctx && "isFile" in ctx;
1345
}
1346
}
1347
1348
export interface Repository {
1349
host: string;
1350
owner: string;
1351
name: string;
1352
cloneUrl: string;
1353
/* Optional kind to differentiate between repositories of orgs/groups/projects and personal repos. */
1354
repoKind?: string;
1355
description?: string;
1356
avatarUrl?: string;
1357
webUrl?: string;
1358
defaultBranch?: string;
1359
/** Optional for backwards compatibility */
1360
private?: boolean;
1361
fork?: {
1362
// The direct parent of this fork
1363
parent: Repository;
1364
};
1365
/**
1366
* Optional date when the repository was last pushed to.
1367
*/
1368
pushedAt?: string;
1369
/**
1370
* Optional display name (e.g. for Bitbucket)
1371
*/
1372
displayName?: string;
1373
}
1374
1375
export interface RepositoryInfo {
1376
url: string;
1377
name: string;
1378
}
1379
1380
export interface Branch {
1381
name: string;
1382
commit: CommitInfo;
1383
htmlUrl: string;
1384
}
1385
1386
export interface CommitInfo {
1387
author: string;
1388
sha: string;
1389
commitMessage: string;
1390
authorAvatarUrl?: string;
1391
authorDate?: string;
1392
}
1393
1394
export interface WorkspaceInstancePortsChangedEvent {
1395
type: "PortsChanged";
1396
instanceID: string;
1397
portsOpened: number[];
1398
portsClosed: number[];
1399
}
1400
1401
export namespace WorkspaceInstancePortsChangedEvent {
1402
export function is(data: any): data is WorkspaceInstancePortsChangedEvent {
1403
return data && data.type == "PortsChanged";
1404
}
1405
}
1406
1407
export interface WorkspaceSession {
1408
workspace: Workspace;
1409
instance: WorkspaceInstance;
1410
metrics?: WorkspaceInstanceMetrics;
1411
}
1412
export interface WorkspaceInfo {
1413
workspace: Workspace;
1414
latestInstance?: WorkspaceInstance;
1415
}
1416
1417
export namespace WorkspaceInfo {
1418
export function lastActiveISODate(info: WorkspaceInfo): string {
1419
return info.latestInstance?.creationTime || info.workspace.creationTime;
1420
}
1421
}
1422
1423
export type RunningWorkspaceInfo = WorkspaceInfo & { latestInstance: WorkspaceInstance };
1424
1425
export interface WorkspaceCreationResult {
1426
createdWorkspaceId?: string;
1427
workspaceURL?: string;
1428
existingWorkspaces?: WorkspaceInfo[];
1429
}
1430
1431
export namespace WorkspaceCreationResult {
1432
export function is(data: any): data is WorkspaceCreationResult {
1433
return (
1434
data &&
1435
("createdWorkspaceId" in data ||
1436
"existingWorkspaces" in data ||
1437
"runningWorkspacePrebuild" in data ||
1438
"runningPrebuildWorkspaceID" in data)
1439
);
1440
}
1441
}
1442
1443
export interface AuthProviderInfo {
1444
readonly authProviderId: string;
1445
readonly authProviderType: string;
1446
readonly host: string;
1447
readonly ownerId?: string;
1448
readonly organizationId?: string;
1449
readonly verified: boolean;
1450
readonly hiddenOnDashboard?: boolean;
1451
readonly disallowLogin?: boolean;
1452
readonly icon?: string;
1453
readonly description?: string;
1454
1455
readonly settingsUrl?: string;
1456
readonly scopes?: string[];
1457
readonly requirements?: {
1458
readonly default: string[];
1459
readonly publicRepo: string[];
1460
readonly privateRepo: string[];
1461
};
1462
}
1463
1464
export interface AuthProviderEntry {
1465
readonly id: string;
1466
readonly type: AuthProviderEntry.Type;
1467
readonly host: string;
1468
readonly ownerId: string;
1469
readonly organizationId?: string;
1470
1471
readonly status: AuthProviderEntry.Status;
1472
1473
readonly oauth: OAuth2Config;
1474
/** A random string that is to change whenever oauth changes (enforced on DB level) */
1475
readonly oauthRevision?: string;
1476
}
1477
1478
export interface OAuth2Config {
1479
readonly clientId: string;
1480
readonly clientSecret: string;
1481
readonly callBackUrl: string;
1482
readonly authorizationUrl: string;
1483
readonly tokenUrl: string;
1484
readonly scope?: string;
1485
readonly scopeSeparator?: string;
1486
1487
readonly settingsUrl?: string;
1488
readonly authorizationParams?: { [key: string]: string };
1489
}
1490
1491
export namespace AuthProviderEntry {
1492
export type Type = "GitHub" | "GitLab" | "Bitbucket" | "BitbucketServer" | "AzureDevOps" | string;
1493
export type Status = "pending" | "verified";
1494
1495
/**
1496
* Some auth providers require additional configuration like Azure DevOps.
1497
*/
1498
export interface OAuth2CustomConfig {
1499
/**
1500
* The URL to the authorize endpoint of the provider.
1501
*/
1502
authorizationUrl?: string;
1503
/**
1504
* The URL to the oauth token endpoint of the provider.
1505
*/
1506
tokenUrl?: string;
1507
}
1508
export type NewEntry = Pick<AuthProviderEntry, "ownerId" | "host" | "type"> & {
1509
clientId?: string;
1510
clientSecret?: string;
1511
} & OAuth2CustomConfig;
1512
export type UpdateEntry = Pick<AuthProviderEntry, "id" | "ownerId"> & {
1513
clientId?: string;
1514
clientSecret?: string;
1515
} & OAuth2CustomConfig;
1516
export type NewOrgEntry = NewEntry & {
1517
organizationId: string;
1518
};
1519
export type UpdateOrgEntry = Pick<AuthProviderEntry, "id"> & {
1520
clientId?: string;
1521
clientSecret?: string;
1522
organizationId: string;
1523
} & OAuth2CustomConfig;
1524
export type UpdateOAuth2Config = Pick<OAuth2Config, "clientId" | "clientSecret"> & OAuth2CustomConfig;
1525
export function redact(entry: AuthProviderEntry): AuthProviderEntry {
1526
return {
1527
...entry,
1528
oauth: {
1529
...entry.oauth,
1530
clientSecret: "redacted",
1531
},
1532
};
1533
}
1534
}
1535
1536
export interface Configuration {
1537
readonly isDedicatedInstallation: boolean;
1538
}
1539
1540
export interface StripeConfig {
1541
individualUsagePriceIds: { [currency: string]: string };
1542
teamUsagePriceIds: { [currency: string]: string };
1543
}
1544
1545
export interface LinkedInProfile {
1546
id: string;
1547
firstName: string;
1548
lastName: string;
1549
profilePicture: string;
1550
emailAddress: string;
1551
}
1552
1553
export type SuggestedRepository = {
1554
url: string;
1555
projectId?: string;
1556
projectName?: string;
1557
repositoryName?: string;
1558
};
1559
1560