-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
auth.ts
132 lines (113 loc) Β· 3.51 KB
/
auth.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { ReadableJsonStream } from "./utils/stream.js";
import { GooglePlatformType } from "./types.js";
export type GoogleAbstractedClientOpsMethod = "GET" | "POST";
export type GoogleAbstractedClientOpsResponseType = "json" | "stream";
export type GoogleAbstractedClientOps = {
url?: string;
method?: GoogleAbstractedClientOpsMethod;
headers?: Record<string, string>;
data?: unknown;
responseType?: GoogleAbstractedClientOpsResponseType;
};
export interface GoogleAbstractedClient {
request: (opts: GoogleAbstractedClientOps) => unknown;
getProjectId: () => Promise<string>;
get clientType(): string;
}
export abstract class GoogleAbstractedFetchClient
implements GoogleAbstractedClient
{
abstract get clientType(): string;
abstract getProjectId(): Promise<string>;
abstract request(opts: GoogleAbstractedClientOps): unknown;
async _request(
url: string | undefined,
opts: GoogleAbstractedClientOps,
additionalHeaders: Record<string, string>
) {
if (url == null) throw new Error("Missing URL");
const fetchOptions: {
method?: string;
headers: Record<string, string>;
body?: string;
} = {
method: opts.method,
headers: {
"Content-Type": "application/json",
...(opts.headers ?? {}),
...(additionalHeaders ?? {}),
},
};
if (opts.data !== undefined) {
fetchOptions.body = JSON.stringify(opts.data);
}
const res = await fetch(url, fetchOptions);
if (!res.ok) {
const resText = await res.text();
const error = new Error(
`Google request failed with status code ${res.status}: ${resText}`
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(error as any).response = res;
throw error;
}
return {
data:
opts.responseType === "json"
? await res.json()
: new ReadableJsonStream(res.body),
config: {},
status: res.status,
statusText: res.statusText,
headers: res.headers,
request: { responseURL: res.url },
};
}
}
export class ApiKeyGoogleAuth extends GoogleAbstractedFetchClient {
apiKey: string;
constructor(apiKey: string) {
super();
this.apiKey = apiKey;
}
get clientType(): string {
return "apiKey";
}
getProjectId(): Promise<string> {
throw new Error("APIs that require a project ID cannot use an API key");
// Perhaps we could implement this if needed:
// https://cloud.google.com/docs/authentication/api-keys#get-info
}
request(opts: GoogleAbstractedClientOps): unknown {
const authHeader = {
"X-Goog-Api-Key": this.apiKey,
};
return this._request(opts.url, opts, authHeader);
}
}
export function aiPlatformScope(platform: GooglePlatformType): string[] {
switch (platform) {
case "gai":
return ["https://www.googleapis.com/auth/generative-language"];
default:
return ["https://www.googleapis.com/auth/cloud-platform"];
}
}
export function ensureAuthOptionScopes<AuthOptions>(
authOption: AuthOptions | undefined,
scopeProperty: string,
scopesOrPlatform: string[] | GooglePlatformType | undefined
): AuthOptions {
// If the property is already set, return it
if (authOption && Object.hasOwn(authOption, scopeProperty)) {
return authOption;
}
// Otherwise add it
const scopes: string[] = Array.isArray(scopesOrPlatform)
? (scopesOrPlatform as string[])
: aiPlatformScope(scopesOrPlatform ?? "gcp");
return {
[scopeProperty]: scopes,
...(authOption ?? {}),
} as AuthOptions;
}