@logto/connector-github 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,7 +55,7 @@ Fill out the `clientId` and `clientSecret` field with _Client ID_ and _Client Se
55
55
 
56
56
  ## Test GitHub connector
57
57
 
58
- That's it. The GitHub connector should be available now. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/tutorials/get-started/passwordless-sign-in-by-adding-connectors#enable-social-sign-in).
58
+ That's it. The GitHub connector should be available now. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/recipes/configure-connectors/social-connector/enable-social-sign-in/).
59
59
 
60
60
  ## Reference
61
61
 
package/lib/index.js CHANGED
@@ -1,809 +1,212 @@
1
- import { ConnectorPlatform, ConnectorConfigFormItemType, ConnectorError, ConnectorErrorCodes, ConnectorType, validateConfig, jsonGuard } from '@logto/connector-kit';
2
- import { z } from 'zod';
3
-
4
- // https://github.com/facebook/jest/issues/7547
5
- const assert = (value, error) => {
6
- if (!value) {
7
- // https://github.com/typescript-eslint/typescript-eslint/issues/3814
8
- // eslint-disable-next-line @typescript-eslint/no-throw-literal
9
- throw error;
10
- }
11
- };
12
-
13
- // eslint-disable-next-line id-length
14
- // eslint-disable-next-line unicorn/prefer-native-coercion-functions
15
- const notFalsy = (value) => Boolean(value);
16
-
17
- /**
18
- * Conditional return the expression result when it's not {@link Falsy};
19
- * otherwise return `undefined`.
20
- *
21
- * @example
22
- * ```ts
23
- * conditional(1 && '2') // '2'
24
- * conditional(false && '1') // undefined
25
- * ```
26
- */
27
- const conditional = (exp) => (notFalsy(exp) ? exp : undefined);
28
-
29
- // Intended
30
- // eslint-disable-next-line @typescript-eslint/no-empty-function
31
- const isPromise = (value) => value !== null &&
32
- (typeof value === 'object' || typeof value === 'function') &&
33
- 'then' in value &&
34
- typeof value.then === 'function';
35
- // eslint-disable-next-line @typescript-eslint/promise-function-async
36
- const trySafe = (exec, onError) => {
37
- try {
38
- const unwrapped = typeof exec === 'function' ? exec() : exec;
39
- return isPromise(unwrapped)
40
- ? // eslint-disable-next-line promise/prefer-await-to-then
41
- unwrapped.catch((error) => {
42
- onError?.(error);
43
- })
44
- : unwrapped;
45
- }
46
- catch (error) {
47
- onError?.(error);
48
- }
49
- };
50
-
51
- // eslint-lint-disable-next-line @typescript-eslint/naming-convention
52
- class HTTPError extends Error {
53
- constructor(response, request, options) {
54
- const code = (response.status || response.status === 0) ? response.status : '';
55
- const title = response.statusText || '';
56
- const status = `${code} ${title}`.trim();
57
- const reason = status ? `status code ${status}` : 'an unknown error';
58
- super(`Request failed with ${reason}`);
59
- Object.defineProperty(this, "response", {
60
- enumerable: true,
61
- configurable: true,
62
- writable: true,
63
- value: void 0
64
- });
65
- Object.defineProperty(this, "request", {
66
- enumerable: true,
67
- configurable: true,
68
- writable: true,
69
- value: void 0
70
- });
71
- Object.defineProperty(this, "options", {
72
- enumerable: true,
73
- configurable: true,
74
- writable: true,
75
- value: void 0
76
- });
77
- this.name = 'HTTPError';
78
- this.response = response;
79
- this.request = request;
80
- this.options = options;
81
- }
82
- }
83
-
84
- class TimeoutError extends Error {
85
- constructor(request) {
86
- super('Request timed out');
87
- Object.defineProperty(this, "request", {
88
- enumerable: true,
89
- configurable: true,
90
- writable: true,
91
- value: void 0
92
- });
93
- this.name = 'TimeoutError';
94
- this.request = request;
95
- }
96
- }
97
-
98
- // eslint-disable-next-line @typescript-eslint/ban-types
99
- const isObject = (value) => value !== null && typeof value === 'object';
100
-
101
- const validateAndMerge = (...sources) => {
102
- for (const source of sources) {
103
- if ((!isObject(source) || Array.isArray(source)) && source !== undefined) {
104
- throw new TypeError('The `options` argument must be an object');
105
- }
106
- }
107
- return deepMerge({}, ...sources);
108
- };
109
- const mergeHeaders = (source1 = {}, source2 = {}) => {
110
- const result = new globalThis.Headers(source1);
111
- const isHeadersInstance = source2 instanceof globalThis.Headers;
112
- const source = new globalThis.Headers(source2);
113
- for (const [key, value] of source.entries()) {
114
- if ((isHeadersInstance && value === 'undefined') || value === undefined) {
115
- result.delete(key);
116
- }
117
- else {
118
- result.set(key, value);
119
- }
120
- }
121
- return result;
122
- };
123
- // TODO: Make this strongly-typed (no `any`).
124
- const deepMerge = (...sources) => {
125
- let returnValue = {};
126
- let headers = {};
127
- for (const source of sources) {
128
- if (Array.isArray(source)) {
129
- if (!Array.isArray(returnValue)) {
130
- returnValue = [];
131
- }
132
- returnValue = [...returnValue, ...source];
133
- }
134
- else if (isObject(source)) {
135
- for (let [key, value] of Object.entries(source)) {
136
- if (isObject(value) && key in returnValue) {
137
- value = deepMerge(returnValue[key], value);
138
- }
139
- returnValue = { ...returnValue, [key]: value };
140
- }
141
- if (isObject(source.headers)) {
142
- headers = mergeHeaders(headers, source.headers);
143
- returnValue.headers = headers;
144
- }
145
- }
146
- }
147
- return returnValue;
148
- };
149
-
150
- const supportsRequestStreams = (() => {
151
- let duplexAccessed = false;
152
- let hasContentType = false;
153
- const supportsReadableStream = typeof globalThis.ReadableStream === 'function';
154
- const supportsRequest = typeof globalThis.Request === 'function';
155
- if (supportsReadableStream && supportsRequest) {
156
- hasContentType = new globalThis.Request('https://empty.invalid', {
157
- body: new globalThis.ReadableStream(),
158
- method: 'POST',
159
- // @ts-expect-error - Types are outdated.
160
- get duplex() {
161
- duplexAccessed = true;
162
- return 'half';
163
- },
164
- }).headers.has('Content-Type');
165
- }
166
- return duplexAccessed && !hasContentType;
167
- })();
168
- const supportsAbortController = typeof globalThis.AbortController === 'function';
169
- const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
170
- const supportsFormData = typeof globalThis.FormData === 'function';
171
- const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'];
172
- const responseTypes = {
173
- json: 'application/json',
174
- text: 'text/*',
175
- formData: 'multipart/form-data',
176
- arrayBuffer: '*/*',
177
- blob: '*/*',
178
- };
179
- // The maximum value of a 32bit int (see issue #117)
180
- const maxSafeTimeout = 2_147_483_647;
181
- const stop = Symbol('stop');
182
- const kyOptionKeys = {
183
- json: true,
184
- parseJson: true,
185
- searchParams: true,
186
- prefixUrl: true,
187
- retry: true,
188
- timeout: true,
189
- hooks: true,
190
- throwHttpErrors: true,
191
- onDownloadProgress: true,
192
- fetch: true,
193
- };
194
- const requestOptionsRegistry = {
195
- method: true,
196
- headers: true,
197
- body: true,
198
- mode: true,
199
- credentials: true,
200
- cache: true,
201
- redirect: true,
202
- referrer: true,
203
- referrerPolicy: true,
204
- integrity: true,
205
- keepalive: true,
206
- signal: true,
207
- window: true,
208
- dispatcher: true,
209
- duplex: true,
210
- priority: true,
211
- };
212
-
213
- const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
214
- const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace'];
215
- const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
216
- const retryAfterStatusCodes = [413, 429, 503];
217
- const defaultRetryOptions = {
218
- limit: 2,
219
- methods: retryMethods,
220
- statusCodes: retryStatusCodes,
221
- afterStatusCodes: retryAfterStatusCodes,
222
- maxRetryAfter: Number.POSITIVE_INFINITY,
223
- backoffLimit: Number.POSITIVE_INFINITY,
224
- delay: attemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000,
225
- };
226
- const normalizeRetryOptions = (retry = {}) => {
227
- if (typeof retry === 'number') {
228
- return {
229
- ...defaultRetryOptions,
230
- limit: retry,
231
- };
232
- }
233
- if (retry.methods && !Array.isArray(retry.methods)) {
234
- throw new Error('retry.methods must be an array');
235
- }
236
- if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
237
- throw new Error('retry.statusCodes must be an array');
238
- }
239
- return {
240
- ...defaultRetryOptions,
241
- ...retry,
242
- afterStatusCodes: retryAfterStatusCodes,
243
- };
244
- };
245
-
246
- // `Promise.race()` workaround (#91)
247
- async function timeout(request, init, abortController, options) {
248
- return new Promise((resolve, reject) => {
249
- const timeoutId = setTimeout(() => {
250
- if (abortController) {
251
- abortController.abort();
252
- }
253
- reject(new TimeoutError(request));
254
- }, options.timeout);
255
- void options
256
- .fetch(request, init)
257
- .then(resolve)
258
- .catch(reject)
259
- .then(() => {
260
- clearTimeout(timeoutId);
261
- });
262
- });
263
- }
264
-
265
- // https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111
266
- async function delay(ms, { signal }) {
267
- return new Promise((resolve, reject) => {
268
- if (signal) {
269
- signal.throwIfAborted();
270
- signal.addEventListener('abort', abortHandler, { once: true });
271
- }
272
- function abortHandler() {
273
- clearTimeout(timeoutId);
274
- reject(signal.reason);
275
- }
276
- const timeoutId = setTimeout(() => {
277
- signal?.removeEventListener('abort', abortHandler);
278
- resolve();
279
- }, ms);
280
- });
281
- }
282
-
283
- const findUnknownOptions = (request, options) => {
284
- const unknownOptions = {};
285
- for (const key in options) {
286
- if (!(key in requestOptionsRegistry) && !(key in kyOptionKeys) && !(key in request)) {
287
- unknownOptions[key] = options[key];
288
- }
289
- }
290
- return unknownOptions;
291
- };
292
-
293
- class Ky {
294
- static create(input, options) {
295
- const ky = new Ky(input, options);
296
- const function_ = async () => {
297
- if (typeof ky._options.timeout === 'number' && ky._options.timeout > maxSafeTimeout) {
298
- throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
299
- }
300
- // Delay the fetch so that body method shortcuts can set the Accept header
301
- await Promise.resolve();
302
- let response = await ky._fetch();
303
- for (const hook of ky._options.hooks.afterResponse) {
304
- // eslint-disable-next-line no-await-in-loop
305
- const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone()));
306
- if (modifiedResponse instanceof globalThis.Response) {
307
- response = modifiedResponse;
308
- }
309
- }
310
- ky._decorateResponse(response);
311
- if (!response.ok && ky._options.throwHttpErrors) {
312
- let error = new HTTPError(response, ky.request, ky._options);
313
- for (const hook of ky._options.hooks.beforeError) {
314
- // eslint-disable-next-line no-await-in-loop
315
- error = await hook(error);
316
- }
317
- throw error;
318
- }
319
- // If `onDownloadProgress` is passed, it uses the stream API internally
320
- /* istanbul ignore next */
321
- if (ky._options.onDownloadProgress) {
322
- if (typeof ky._options.onDownloadProgress !== 'function') {
323
- throw new TypeError('The `onDownloadProgress` option must be a function');
324
- }
325
- if (!supportsResponseStreams) {
326
- throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
327
- }
328
- return ky._stream(response.clone(), ky._options.onDownloadProgress);
329
- }
330
- return response;
331
- };
332
- const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
333
- const result = (isRetriableMethod ? ky._retry(function_) : function_());
334
- for (const [type, mimeType] of Object.entries(responseTypes)) {
335
- result[type] = async () => {
336
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
337
- ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
338
- const awaitedResult = await result;
339
- const response = awaitedResult.clone();
340
- if (type === 'json') {
341
- if (response.status === 204) {
342
- return '';
343
- }
344
- const arrayBuffer = await response.clone().arrayBuffer();
345
- const responseSize = arrayBuffer.byteLength;
346
- if (responseSize === 0) {
347
- return '';
348
- }
349
- if (options.parseJson) {
350
- return options.parseJson(await response.text());
351
- }
352
- }
353
- return response[type]();
354
- };
355
- }
356
- return result;
357
- }
358
- // eslint-disable-next-line complexity
359
- constructor(input, options = {}) {
360
- Object.defineProperty(this, "request", {
361
- enumerable: true,
362
- configurable: true,
363
- writable: true,
364
- value: void 0
365
- });
366
- Object.defineProperty(this, "abortController", {
367
- enumerable: true,
368
- configurable: true,
369
- writable: true,
370
- value: void 0
371
- });
372
- Object.defineProperty(this, "_retryCount", {
373
- enumerable: true,
374
- configurable: true,
375
- writable: true,
376
- value: 0
377
- });
378
- Object.defineProperty(this, "_input", {
379
- enumerable: true,
380
- configurable: true,
381
- writable: true,
382
- value: void 0
383
- });
384
- Object.defineProperty(this, "_options", {
385
- enumerable: true,
386
- configurable: true,
387
- writable: true,
388
- value: void 0
389
- });
390
- this._input = input;
391
- const credentials = this._input instanceof Request && 'credentials' in Request.prototype
392
- ? this._input.credentials
393
- : undefined;
394
- this._options = {
395
- ...(credentials && { credentials }), // For exactOptionalPropertyTypes
396
- ...options,
397
- headers: mergeHeaders(this._input.headers, options.headers),
398
- hooks: deepMerge({
399
- beforeRequest: [],
400
- beforeRetry: [],
401
- beforeError: [],
402
- afterResponse: [],
403
- }, options.hooks),
404
- method: normalizeRequestMethod(options.method ?? this._input.method),
405
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
406
- prefixUrl: String(options.prefixUrl || ''),
407
- retry: normalizeRetryOptions(options.retry),
408
- throwHttpErrors: options.throwHttpErrors !== false,
409
- timeout: options.timeout ?? 10_000,
410
- fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
411
- };
412
- if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
413
- throw new TypeError('`input` must be a string, URL, or Request');
414
- }
415
- if (this._options.prefixUrl && typeof this._input === 'string') {
416
- if (this._input.startsWith('/')) {
417
- throw new Error('`input` must not begin with a slash when using `prefixUrl`');
418
- }
419
- if (!this._options.prefixUrl.endsWith('/')) {
420
- this._options.prefixUrl += '/';
421
- }
422
- this._input = this._options.prefixUrl + this._input;
423
- }
424
- if (supportsAbortController) {
425
- this.abortController = new globalThis.AbortController();
426
- if (this._options.signal) {
427
- const originalSignal = this._options.signal;
428
- this._options.signal.addEventListener('abort', () => {
429
- this.abortController.abort(originalSignal.reason);
430
- });
431
- }
432
- this._options.signal = this.abortController.signal;
433
- }
434
- if (supportsRequestStreams) {
435
- // @ts-expect-error - Types are outdated.
436
- this._options.duplex = 'half';
437
- }
438
- this.request = new globalThis.Request(this._input, this._options);
439
- if (this._options.searchParams) {
440
- // eslint-disable-next-line unicorn/prevent-abbreviations
441
- const textSearchParams = typeof this._options.searchParams === 'string'
442
- ? this._options.searchParams.replace(/^\?/, '')
443
- : new URLSearchParams(this._options.searchParams).toString();
444
- // eslint-disable-next-line unicorn/prevent-abbreviations
445
- const searchParams = '?' + textSearchParams;
446
- const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
447
- // To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
448
- if (((supportsFormData && this._options.body instanceof globalThis.FormData)
449
- || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
450
- this.request.headers.delete('content-type');
451
- }
452
- // The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws.
453
- this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
454
- }
455
- if (this._options.json !== undefined) {
456
- this._options.body = JSON.stringify(this._options.json);
457
- this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json');
458
- this.request = new globalThis.Request(this.request, { body: this._options.body });
459
- }
460
- }
461
- _calculateRetryDelay(error) {
462
- this._retryCount++;
463
- if (this._retryCount <= this._options.retry.limit && !(error instanceof TimeoutError)) {
464
- if (error instanceof HTTPError) {
465
- if (!this._options.retry.statusCodes.includes(error.response.status)) {
466
- return 0;
467
- }
468
- const retryAfter = error.response.headers.get('Retry-After');
469
- if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
470
- let after = Number(retryAfter);
471
- if (Number.isNaN(after)) {
472
- after = Date.parse(retryAfter) - Date.now();
473
- }
474
- else {
475
- after *= 1000;
476
- }
477
- if (this._options.retry.maxRetryAfter !== undefined && after > this._options.retry.maxRetryAfter) {
478
- return 0;
479
- }
480
- return after;
481
- }
482
- if (error.response.status === 413) {
483
- return 0;
484
- }
485
- }
486
- const retryDelay = this._options.retry.delay(this._retryCount);
487
- return Math.min(this._options.retry.backoffLimit, retryDelay);
488
- }
489
- return 0;
490
- }
491
- _decorateResponse(response) {
492
- if (this._options.parseJson) {
493
- response.json = async () => this._options.parseJson(await response.text());
494
- }
495
- return response;
496
- }
497
- async _retry(function_) {
498
- try {
499
- return await function_();
500
- }
501
- catch (error) {
502
- const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
503
- if (ms !== 0 && this._retryCount > 0) {
504
- await delay(ms, { signal: this._options.signal });
505
- for (const hook of this._options.hooks.beforeRetry) {
506
- // eslint-disable-next-line no-await-in-loop
507
- const hookResult = await hook({
508
- request: this.request,
509
- options: this._options,
510
- error: error,
511
- retryCount: this._retryCount,
512
- });
513
- // If `stop` is returned from the hook, the retry process is stopped
514
- if (hookResult === stop) {
515
- return;
516
- }
517
- }
518
- return this._retry(function_);
519
- }
520
- throw error;
521
- }
522
- }
523
- async _fetch() {
524
- for (const hook of this._options.hooks.beforeRequest) {
525
- // eslint-disable-next-line no-await-in-loop
526
- const result = await hook(this.request, this._options);
527
- if (result instanceof Request) {
528
- this.request = result;
529
- break;
530
- }
531
- if (result instanceof Response) {
532
- return result;
533
- }
534
- }
535
- const nonRequestOptions = findUnknownOptions(this.request, this._options);
536
- if (this._options.timeout === false) {
537
- return this._options.fetch(this.request.clone(), nonRequestOptions);
538
- }
539
- return timeout(this.request.clone(), nonRequestOptions, this.abortController, this._options);
540
- }
541
- /* istanbul ignore next */
542
- _stream(response, onDownloadProgress) {
543
- const totalBytes = Number(response.headers.get('content-length')) || 0;
544
- let transferredBytes = 0;
545
- if (response.status === 204) {
546
- if (onDownloadProgress) {
547
- onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
548
- }
549
- return new globalThis.Response(null, {
550
- status: response.status,
551
- statusText: response.statusText,
552
- headers: response.headers,
553
- });
554
- }
555
- return new globalThis.Response(new globalThis.ReadableStream({
556
- async start(controller) {
557
- const reader = response.body.getReader();
558
- if (onDownloadProgress) {
559
- onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
560
- }
561
- async function read() {
562
- const { done, value } = await reader.read();
563
- if (done) {
564
- controller.close();
565
- return;
566
- }
567
- if (onDownloadProgress) {
568
- transferredBytes += value.byteLength;
569
- const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
570
- onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
571
- }
572
- controller.enqueue(value);
573
- await read();
574
- }
575
- await read();
576
- },
577
- }), {
578
- status: response.status,
579
- statusText: response.statusText,
580
- headers: response.headers,
581
- });
582
- }
583
- }
584
-
585
- /*! MIT License © Sindre Sorhus */
586
- const createInstance = (defaults) => {
587
- // eslint-disable-next-line @typescript-eslint/promise-function-async
588
- const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
589
- for (const method of requestMethods) {
590
- // eslint-disable-next-line @typescript-eslint/promise-function-async
591
- ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
592
- }
593
- ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
594
- ky.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
595
- ky.stop = stop;
596
- return ky;
597
- };
598
- const ky = createInstance();
599
-
600
- const authorizationEndpoint = 'https://github.com/login/oauth/authorize';
601
- /**
602
- * `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).
603
- * Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
604
- */
605
- const scope = 'read:user user:email';
606
- const accessTokenEndpoint = 'https://github.com/login/oauth/access_token';
607
- const userInfoEndpoint = 'https://api.github.com/user';
608
- // Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
609
- const userEmailsEndpoint = 'https://api.github.com/user/emails';
610
- const defaultMetadata = {
611
- id: 'github-universal',
612
- target: 'github',
613
- platform: ConnectorPlatform.Universal,
614
- name: {
615
- en: 'GitHub',
616
- 'zh-CN': 'GitHub',
617
- 'tr-TR': 'GitHub',
618
- ko: 'GitHub',
1
+ // src/index.ts
2
+ import { assert, conditional, trySafe } from "@silverhand/essentials";
3
+ import {
4
+ ConnectorError,
5
+ ConnectorErrorCodes,
6
+ validateConfig,
7
+ ConnectorType,
8
+ jsonGuard
9
+ } from "@logto/connector-kit";
10
+ import ky, { HTTPError } from "ky";
11
+
12
+ // src/constant.ts
13
+ import { ConnectorPlatform, ConnectorConfigFormItemType } from "@logto/connector-kit";
14
+ var authorizationEndpoint = "https://github.com/login/oauth/authorize";
15
+ var scope = "read:user user:email";
16
+ var accessTokenEndpoint = "https://github.com/login/oauth/access_token";
17
+ var userInfoEndpoint = "https://api.github.com/user";
18
+ var userEmailsEndpoint = "https://api.github.com/user/emails";
19
+ var defaultMetadata = {
20
+ id: "github-universal",
21
+ target: "github",
22
+ platform: ConnectorPlatform.Universal,
23
+ name: {
24
+ en: "GitHub",
25
+ "zh-CN": "GitHub",
26
+ "tr-TR": "GitHub",
27
+ ko: "GitHub"
28
+ },
29
+ logo: "./logo.svg",
30
+ logoDark: "./logo-dark.svg",
31
+ description: {
32
+ en: "GitHub is an online community for software development and version control.",
33
+ "zh-CN": "GitHub \u662F\u6781\u53D7\u6B22\u8FCE\u7684\u4EE3\u7801\u6258\u7BA1\u4ED3\u5E93\u3002",
34
+ "tr-TR": "GitHub, yaz\u0131l\u0131m geli\u015Ftirme ve s\xFCr\xFCm kontrol\xFC i\xE7in \xE7evrimi\xE7i bir topluluktur.",
35
+ ko: "GitHub\uB294 \uC18C\uD504\uD2B8\uC6E8\uC5B4 \uAC1C\uBC1C\uACFC \uBC84\uC804 \uAD00\uB9AC\uB97C \uC704\uD55C \uC628\uB77C\uC778 \uCEE4\uBBA4\uB2C8\uD2F0\uC785\uB2C8\uB2E4."
36
+ },
37
+ readme: "./README.md",
38
+ formItems: [
39
+ {
40
+ key: "clientId",
41
+ type: ConnectorConfigFormItemType.Text,
42
+ label: "Client ID",
43
+ required: true,
44
+ placeholder: "<client-id>"
619
45
  },
620
- logo: './logo.svg',
621
- logoDark: './logo-dark.svg',
622
- description: {
623
- en: 'GitHub is an online community for software development and version control.',
624
- 'zh-CN': 'GitHub 是极受欢迎的代码托管仓库。',
625
- 'tr-TR': 'GitHub, yazılım geliştirme ve sürüm kontrolü için çevrimiçi bir topluluktur.',
626
- ko: 'GitHub는 소프트웨어 개발과 버전 관리를 위한 온라인 커뮤니티입니다.',
46
+ {
47
+ key: "clientSecret",
48
+ type: ConnectorConfigFormItemType.Text,
49
+ label: "Client Secret",
50
+ required: true,
51
+ placeholder: "<client-secret>"
627
52
  },
628
- readme: './README.md',
629
- formItems: [
630
- {
631
- key: 'clientId',
632
- type: ConnectorConfigFormItemType.Text,
633
- label: 'Client ID',
634
- required: true,
635
- placeholder: '<client-id>',
636
- },
637
- {
638
- key: 'clientSecret',
639
- type: ConnectorConfigFormItemType.Text,
640
- label: 'Client Secret',
641
- required: true,
642
- placeholder: '<client-secret>',
643
- },
644
- {
645
- key: 'scope',
646
- type: ConnectorConfigFormItemType.Text,
647
- label: 'Scope',
648
- required: false,
649
- placeholder: '<scope>',
650
- description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.",
651
- },
652
- ],
653
- };
654
- const defaultTimeout = 5000;
655
-
656
- const githubConfigGuard = z.object({
657
- clientId: z.string(),
658
- clientSecret: z.string(),
659
- scope: z.string().optional(),
53
+ {
54
+ key: "scope",
55
+ type: ConnectorConfigFormItemType.Text,
56
+ label: "Scope",
57
+ required: false,
58
+ placeholder: "<scope>",
59
+ description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank."
60
+ }
61
+ ]
62
+ };
63
+ var defaultTimeout = 5e3;
64
+
65
+ // src/types.ts
66
+ import { z } from "zod";
67
+ var githubConfigGuard = z.object({
68
+ clientId: z.string(),
69
+ clientSecret: z.string(),
70
+ scope: z.string().optional()
660
71
  });
661
- /**
662
- * This guard is used to validate the response from the GitHub API when requesting the user's email addresses.
663
- * Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
664
- */
665
- const emailAddressGuard = z.object({
666
- email: z.string(),
667
- primary: z.boolean(),
668
- verified: z.boolean(),
669
- visibility: z.string().nullable(),
72
+ var emailAddressGuard = z.object({
73
+ email: z.string(),
74
+ primary: z.boolean(),
75
+ verified: z.boolean(),
76
+ visibility: z.string().nullable()
670
77
  });
671
- const accessTokenResponseGuard = z.object({
672
- access_token: z.string(),
673
- scope: z.string(),
674
- token_type: z.string(),
78
+ var accessTokenResponseGuard = z.object({
79
+ access_token: z.string(),
80
+ scope: z.string(),
81
+ token_type: z.string()
675
82
  });
676
- const userInfoResponseGuard = z.object({
677
- id: z.number(),
678
- avatar_url: z.string().optional().nullable(),
679
- email: z.string().optional().nullable(),
680
- name: z.string().optional().nullable(),
83
+ var userInfoResponseGuard = z.object({
84
+ id: z.number(),
85
+ avatar_url: z.string().optional().nullable(),
86
+ email: z.string().optional().nullable(),
87
+ name: z.string().optional().nullable()
681
88
  });
682
- const authorizationCallbackErrorGuard = z.object({
683
- error: z.string(),
684
- error_description: z.string(),
685
- error_uri: z.string(),
89
+ var authorizationCallbackErrorGuard = z.object({
90
+ error: z.string(),
91
+ error_description: z.string(),
92
+ error_uri: z.string()
686
93
  });
687
- const authResponseGuard = z.object({ code: z.string() });
688
-
689
- const getAuthorizationUri = (getConfig) => async ({ state, redirectUri }) => {
690
- const config = await getConfig(defaultMetadata.id);
691
- validateConfig(config, githubConfigGuard);
692
- const queryParameters = new URLSearchParams({
693
- client_id: config.clientId,
694
- redirect_uri: redirectUri,
695
- state,
696
- scope: config.scope ?? scope,
697
- });
698
- return `${authorizationEndpoint}?${queryParameters.toString()}`;
699
- };
700
- const authorizationCallbackHandler = async (parameterObject) => {
701
- const result = authResponseGuard.safeParse(parameterObject);
702
- if (result.success) {
703
- return result.data;
704
- }
705
- const parsedError = authorizationCallbackErrorGuard.safeParse(parameterObject);
706
- if (!parsedError.success) {
707
- throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
708
- }
709
- const { error, error_description, error_uri } = parsedError.data;
710
- if (error === 'access_denied') {
711
- throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed, error_description);
712
- }
713
- throw new ConnectorError(ConnectorErrorCodes.General, {
714
- error,
715
- errorDescription: error_description,
716
- error_uri,
717
- });
718
- };
719
- const getAccessToken = async (config, codeObject) => {
720
- const { code } = codeObject;
721
- const { clientId: client_id, clientSecret: client_secret } = config;
722
- const httpResponse = await ky
723
- .post(accessTokenEndpoint, {
724
- body: new URLSearchParams({
725
- client_id,
726
- client_secret,
727
- code,
728
- }),
729
- timeout: defaultTimeout,
730
- })
731
- .json();
732
- const result = accessTokenResponseGuard.safeParse(httpResponse);
733
- if (!result.success) {
734
- throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
735
- }
736
- const { access_token: accessToken } = result.data;
737
- assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
738
- return { accessToken };
739
- };
740
- const getUserInfo = (getConfig) => async (data) => {
741
- const { code } = await authorizationCallbackHandler(data);
742
- const config = await getConfig(defaultMetadata.id);
743
- validateConfig(config, githubConfigGuard);
744
- const { accessToken } = await getAccessToken(config, { code });
745
- const authedApi = ky.create({
746
- timeout: defaultTimeout,
747
- hooks: {
748
- beforeRequest: [
749
- (request) => {
750
- request.headers.set('Authorization', `Bearer ${accessToken}`);
751
- },
752
- ],
753
- },
754
- });
755
- try {
756
- /**
757
- * If user(s) is using GitHub Apps (instead of OAuth Apps), they can customize
758
- * "Account permissions" and restrict the "email addresses" visibility, and GitHub
759
- * hence throws error instead of returning an empty array.
760
- *
761
- * We try catch the error and return an empty array instead.
762
- */
763
- const [userInfo, userEmails = []] = await Promise.all([
764
- authedApi.get(userInfoEndpoint).json(),
765
- trySafe(authedApi.get(userEmailsEndpoint).json()),
766
- ]);
767
- const userInfoResult = userInfoResponseGuard.safeParse(userInfo);
768
- const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);
769
- if (!userInfoResult.success) {
770
- throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
771
- }
772
- if (!userEmailsResult.success) {
773
- throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);
774
- }
775
- const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;
776
- return {
777
- id: String(id),
778
- avatar: conditional(avatar),
779
- email: conditional(publicEmail ??
780
- userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email),
781
- name: conditional(name),
782
- rawData: jsonGuard.parse({
783
- userInfo,
784
- userEmails,
785
- }),
786
- };
787
- }
788
- catch (error) {
789
- if (error instanceof HTTPError) {
790
- const { status, body: rawBody } = error.response;
791
- if (status === 401) {
792
- throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
793
- }
794
- throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
795
- }
796
- throw error;
797
- }
798
- };
799
- const createGithubConnector = async ({ getConfig }) => {
94
+ var authResponseGuard = z.object({ code: z.string() });
95
+
96
+ // src/index.ts
97
+ var getAuthorizationUri = (getConfig) => async ({ state, redirectUri }) => {
98
+ const config = await getConfig(defaultMetadata.id);
99
+ validateConfig(config, githubConfigGuard);
100
+ const queryParameters = new URLSearchParams({
101
+ client_id: config.clientId,
102
+ redirect_uri: redirectUri,
103
+ state,
104
+ scope: config.scope ?? scope
105
+ });
106
+ return `${authorizationEndpoint}?${queryParameters.toString()}`;
107
+ };
108
+ var authorizationCallbackHandler = async (parameterObject) => {
109
+ const result = authResponseGuard.safeParse(parameterObject);
110
+ if (result.success) {
111
+ return result.data;
112
+ }
113
+ const parsedError = authorizationCallbackErrorGuard.safeParse(parameterObject);
114
+ if (!parsedError.success) {
115
+ throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
116
+ }
117
+ const { error, error_description, error_uri } = parsedError.data;
118
+ if (error === "access_denied") {
119
+ throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed, error_description);
120
+ }
121
+ throw new ConnectorError(ConnectorErrorCodes.General, {
122
+ error,
123
+ errorDescription: error_description,
124
+ error_uri
125
+ });
126
+ };
127
+ var getAccessToken = async (config, codeObject) => {
128
+ const { code } = codeObject;
129
+ const { clientId: client_id, clientSecret: client_secret } = config;
130
+ const httpResponse = await ky.post(accessTokenEndpoint, {
131
+ body: new URLSearchParams({
132
+ client_id,
133
+ client_secret,
134
+ code
135
+ }),
136
+ timeout: defaultTimeout
137
+ }).json();
138
+ const result = accessTokenResponseGuard.safeParse(httpResponse);
139
+ if (!result.success) {
140
+ throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
141
+ }
142
+ const { access_token: accessToken } = result.data;
143
+ assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
144
+ return { accessToken };
145
+ };
146
+ var getUserInfo = (getConfig) => async (data) => {
147
+ const { code } = await authorizationCallbackHandler(data);
148
+ const config = await getConfig(defaultMetadata.id);
149
+ validateConfig(config, githubConfigGuard);
150
+ const { accessToken } = await getAccessToken(config, { code });
151
+ const authedApi = ky.create({
152
+ timeout: defaultTimeout,
153
+ hooks: {
154
+ beforeRequest: [
155
+ (request) => {
156
+ request.headers.set("Authorization", `Bearer ${accessToken}`);
157
+ }
158
+ ]
159
+ }
160
+ });
161
+ try {
162
+ const [userInfo, userEmails = []] = await Promise.all([
163
+ authedApi.get(userInfoEndpoint).json(),
164
+ trySafe(authedApi.get(userEmailsEndpoint).json())
165
+ ]);
166
+ const userInfoResult = userInfoResponseGuard.safeParse(userInfo);
167
+ const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);
168
+ if (!userInfoResult.success) {
169
+ throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
170
+ }
171
+ if (!userEmailsResult.success) {
172
+ throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);
173
+ }
174
+ const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;
800
175
  return {
801
- metadata: defaultMetadata,
802
- type: ConnectorType.Social,
803
- configGuard: githubConfigGuard,
804
- getAuthorizationUri: getAuthorizationUri(getConfig),
805
- getUserInfo: getUserInfo(getConfig),
176
+ id: String(id),
177
+ avatar: conditional(avatar),
178
+ email: conditional(
179
+ publicEmail ?? userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email
180
+ ),
181
+ name: conditional(name),
182
+ rawData: jsonGuard.parse({
183
+ userInfo,
184
+ userEmails
185
+ })
806
186
  };
807
- };
808
-
809
- export { createGithubConnector as default, getAccessToken };
187
+ } catch (error) {
188
+ if (error instanceof HTTPError) {
189
+ const { status, body: rawBody } = error.response;
190
+ if (status === 401) {
191
+ throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
192
+ }
193
+ throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
194
+ }
195
+ throw error;
196
+ }
197
+ };
198
+ var createGithubConnector = async ({ getConfig }) => {
199
+ return {
200
+ metadata: defaultMetadata,
201
+ type: ConnectorType.Social,
202
+ configGuard: githubConfigGuard,
203
+ getAuthorizationUri: getAuthorizationUri(getConfig),
204
+ getUserInfo: getUserInfo(getConfig)
205
+ };
206
+ };
207
+ var src_default = createGithubConnector;
208
+ export {
209
+ src_default as default,
210
+ getAccessToken
211
+ };
212
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/constant.ts","../src/types.ts"],"sourcesContent":["import { assert, conditional, trySafe } from '@silverhand/essentials';\n\nimport {\n ConnectorError,\n ConnectorErrorCodes,\n validateConfig,\n ConnectorType,\n jsonGuard,\n} from '@logto/connector-kit';\nimport type {\n GetAuthorizationUri,\n GetUserInfo,\n SocialConnector,\n CreateConnector,\n GetConnectorConfig,\n} from '@logto/connector-kit';\nimport ky, { HTTPError } from 'ky';\n\nimport {\n authorizationEndpoint,\n accessTokenEndpoint,\n scope as defaultScope,\n userInfoEndpoint,\n userEmailsEndpoint,\n defaultMetadata,\n defaultTimeout,\n} from './constant.js';\nimport type { GithubConfig } from './types.js';\nimport {\n authorizationCallbackErrorGuard,\n githubConfigGuard,\n emailAddressGuard,\n accessTokenResponseGuard,\n userInfoResponseGuard,\n authResponseGuard,\n} from './types.js';\n\nconst getAuthorizationUri =\n (getConfig: GetConnectorConfig): GetAuthorizationUri =>\n async ({ state, redirectUri }) => {\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, githubConfigGuard);\n const queryParameters = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: redirectUri,\n state,\n scope: config.scope ?? defaultScope,\n });\n\n return `${authorizationEndpoint}?${queryParameters.toString()}`;\n };\n\nconst authorizationCallbackHandler = async (parameterObject: unknown) => {\n const result = authResponseGuard.safeParse(parameterObject);\n\n if (result.success) {\n return result.data;\n }\n\n const parsedError = authorizationCallbackErrorGuard.safeParse(parameterObject);\n\n if (!parsedError.success) {\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));\n }\n\n const { error, error_description, error_uri } = parsedError.data;\n\n if (error === 'access_denied') {\n throw new ConnectorError(ConnectorErrorCodes.AuthorizationFailed, error_description);\n }\n\n throw new ConnectorError(ConnectorErrorCodes.General, {\n error,\n errorDescription: error_description,\n error_uri,\n });\n};\n\nexport const getAccessToken = async (config: GithubConfig, codeObject: { code: string }) => {\n const { code } = codeObject;\n const { clientId: client_id, clientSecret: client_secret } = config;\n\n const httpResponse = await ky\n .post(accessTokenEndpoint, {\n body: new URLSearchParams({\n client_id,\n client_secret,\n code,\n }),\n timeout: defaultTimeout,\n })\n .json();\n\n const result = accessTokenResponseGuard.safeParse(httpResponse);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);\n }\n\n const { access_token: accessToken } = result.data;\n\n assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));\n\n return { accessToken };\n};\n\nconst getUserInfo =\n (getConfig: GetConnectorConfig): GetUserInfo =>\n async (data) => {\n const { code } = await authorizationCallbackHandler(data);\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, githubConfigGuard);\n const { accessToken } = await getAccessToken(config, { code });\n\n const authedApi = ky.create({\n timeout: defaultTimeout,\n hooks: {\n beforeRequest: [\n (request) => {\n request.headers.set('Authorization', `Bearer ${accessToken}`);\n },\n ],\n },\n });\n\n try {\n /**\n * If user(s) is using GitHub Apps (instead of OAuth Apps), they can customize\n * \"Account permissions\" and restrict the \"email addresses\" visibility, and GitHub\n * hence throws error instead of returning an empty array.\n *\n * We try catch the error and return an empty array instead.\n */\n const [userInfo, userEmails = []] = await Promise.all([\n authedApi.get(userInfoEndpoint).json(),\n trySafe(authedApi.get(userEmailsEndpoint).json()),\n ]);\n\n const userInfoResult = userInfoResponseGuard.safeParse(userInfo);\n const userEmailsResult = emailAddressGuard.array().safeParse(userEmails);\n\n if (!userInfoResult.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);\n }\n\n if (!userEmailsResult.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userEmailsResult.error);\n }\n\n const { id, avatar_url: avatar, email: publicEmail, name } = userInfoResult.data;\n\n return {\n id: String(id),\n avatar: conditional(avatar),\n email: conditional(\n publicEmail ??\n userEmailsResult.data.find(({ verified, primary }) => verified && primary)?.email\n ),\n name: conditional(name),\n rawData: jsonGuard.parse({\n userInfo,\n userEmails,\n }),\n };\n } catch (error: unknown) {\n if (error instanceof HTTPError) {\n const { status, body: rawBody } = error.response;\n\n if (status === 401) {\n throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);\n }\n\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));\n }\n\n throw error;\n }\n };\n\nconst createGithubConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {\n return {\n metadata: defaultMetadata,\n type: ConnectorType.Social,\n configGuard: githubConfigGuard,\n getAuthorizationUri: getAuthorizationUri(getConfig),\n getUserInfo: getUserInfo(getConfig),\n };\n};\n\nexport default createGithubConnector;\n","import type { ConnectorMetadata } from '@logto/connector-kit';\nimport { ConnectorPlatform, ConnectorConfigFormItemType } from '@logto/connector-kit';\n\nexport const authorizationEndpoint = 'https://github.com/login/oauth/authorize';\n/**\n * `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).\n * Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps\n */\nexport const scope = 'read:user user:email';\nexport const accessTokenEndpoint = 'https://github.com/login/oauth/access_token';\nexport const userInfoEndpoint = 'https://api.github.com/user';\n// Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user\nexport const userEmailsEndpoint = 'https://api.github.com/user/emails';\n\nexport const defaultMetadata: ConnectorMetadata = {\n id: 'github-universal',\n target: 'github',\n platform: ConnectorPlatform.Universal,\n name: {\n en: 'GitHub',\n 'zh-CN': 'GitHub',\n 'tr-TR': 'GitHub',\n ko: 'GitHub',\n },\n logo: './logo.svg',\n logoDark: './logo-dark.svg',\n description: {\n en: 'GitHub is an online community for software development and version control.',\n 'zh-CN': 'GitHub 是极受欢迎的代码托管仓库。',\n 'tr-TR': 'GitHub, yazılım geliştirme ve sürüm kontrolü için çevrimiçi bir topluluktur.',\n ko: 'GitHub는 소프트웨어 개발과 버전 관리를 위한 온라인 커뮤니티입니다.',\n },\n readme: './README.md',\n formItems: [\n {\n key: 'clientId',\n type: ConnectorConfigFormItemType.Text,\n label: 'Client ID',\n required: true,\n placeholder: '<client-id>',\n },\n {\n key: 'clientSecret',\n type: ConnectorConfigFormItemType.Text,\n label: 'Client Secret',\n required: true,\n placeholder: '<client-secret>',\n },\n {\n key: 'scope',\n type: ConnectorConfigFormItemType.Text,\n label: 'Scope',\n required: false,\n placeholder: '<scope>',\n description:\n \"The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.\",\n },\n ],\n};\n\nexport const defaultTimeout = 5000;\n","import { z } from 'zod';\n\nexport const githubConfigGuard = z.object({\n clientId: z.string(),\n clientSecret: z.string(),\n scope: z.string().optional(),\n});\n\nexport type GithubConfig = z.infer<typeof githubConfigGuard>;\n\n/**\n * This guard is used to validate the response from the GitHub API when requesting the user's email addresses.\n * Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user\n */\nexport const emailAddressGuard = z.object({\n email: z.string(),\n primary: z.boolean(),\n verified: z.boolean(),\n visibility: z.string().nullable(),\n});\n\nexport const accessTokenResponseGuard = z.object({\n access_token: z.string(),\n scope: z.string(),\n token_type: z.string(),\n});\n\nexport type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;\n\nexport const userInfoResponseGuard = z.object({\n id: z.number(),\n avatar_url: z.string().optional().nullable(),\n email: z.string().optional().nullable(),\n name: z.string().optional().nullable(),\n});\n\nexport type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;\n\nexport const authorizationCallbackErrorGuard = z.object({\n error: z.string(),\n error_description: z.string(),\n error_uri: z.string(),\n});\n\nexport const authResponseGuard = z.object({ code: z.string() });\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@logto/connector-github",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Github web connector implementation.",
5
5
  "author": "Silverhand Inc. <contact@silverhand.io>",
6
6
  "dependencies": {
7
- "@logto/connector-kit": "^3.0.0",
7
+ "@logto/connector-kit": "^4.0.0",
8
8
  "@silverhand/essentials": "^2.9.1",
9
9
  "ky": "^1.2.3",
10
10
  "query-string": "^9.0.0",
11
- "snakecase-keys": "^8.0.0",
12
- "zod": "^3.22.4"
11
+ "snakecase-keys": "^8.0.1",
12
+ "zod": "^3.23.8"
13
13
  },
14
14
  "main": "./lib/index.js",
15
15
  "module": "./lib/index.js",
@@ -42,30 +42,25 @@
42
42
  "access": "public"
43
43
  },
44
44
  "devDependencies": {
45
- "@rollup/plugin-commonjs": "^25.0.7",
46
- "@rollup/plugin-json": "^6.1.0",
47
- "@rollup/plugin-node-resolve": "^15.2.3",
48
- "@rollup/plugin-typescript": "^11.1.6",
49
45
  "@silverhand/eslint-config": "6.0.1",
50
46
  "@silverhand/ts-config": "6.0.0",
51
47
  "@types/node": "^20.11.20",
52
48
  "@types/supertest": "^6.0.2",
53
- "@vitest/coverage-v8": "^1.4.0",
49
+ "@vitest/coverage-v8": "^2.0.0",
54
50
  "eslint": "^8.56.0",
55
51
  "lint-staged": "^15.0.2",
56
- "nock": "14.0.0-beta.7",
52
+ "nock": "14.0.0-beta.9",
57
53
  "prettier": "^3.0.0",
58
- "rollup": "^4.12.0",
59
- "rollup-plugin-output-size": "^1.3.0",
60
54
  "supertest": "^7.0.0",
61
- "typescript": "^5.3.3",
62
- "vitest": "^1.4.0"
55
+ "tsup": "^8.1.0",
56
+ "typescript": "^5.5.3",
57
+ "vitest": "^2.0.0"
63
58
  },
64
59
  "scripts": {
65
60
  "precommit": "lint-staged",
66
- "build:test": "rm -rf lib/ && tsc -p tsconfig.test.json --sourcemap",
67
- "build": "rm -rf lib/ && tsc -p tsconfig.build.json --noEmit && rollup -c",
68
- "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
61
+ "check": "tsc --noEmit",
62
+ "build": "tsup",
63
+ "dev": "tsup --watch",
69
64
  "lint": "eslint --ext .ts src",
70
65
  "lint:report": "pnpm lint --format json --output-file report.json",
71
66
  "test": "vitest src",
package/lib/constant.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { ConnectorMetadata } from '@logto/connector-kit';
2
- export declare const authorizationEndpoint = "https://github.com/login/oauth/authorize";
3
- /**
4
- * `read:user` read user profile data; `user:email` read user email addresses (including private email addresses).
5
- * Ref: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
6
- */
7
- export declare const scope = "read:user user:email";
8
- export declare const accessTokenEndpoint = "https://github.com/login/oauth/access_token";
9
- export declare const userInfoEndpoint = "https://api.github.com/user";
10
- export declare const userEmailsEndpoint = "https://api.github.com/user/emails";
11
- export declare const defaultMetadata: ConnectorMetadata;
12
- export declare const defaultTimeout = 5000;
package/lib/index.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import type { SocialConnector, CreateConnector } from '@logto/connector-kit';
2
- import type { GithubConfig } from './types.js';
3
- export declare const getAccessToken: (config: GithubConfig, codeObject: {
4
- code: string;
5
- }) => Promise<{
6
- accessToken: string;
7
- }>;
8
- declare const createGithubConnector: CreateConnector<SocialConnector>;
9
- export default createGithubConnector;
package/lib/mock.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export declare const mockedConfig: {
2
- clientId: string;
3
- clientSecret: string;
4
- };
package/lib/types.d.ts DELETED
@@ -1,86 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const githubConfigGuard: z.ZodObject<{
3
- clientId: z.ZodString;
4
- clientSecret: z.ZodString;
5
- scope: z.ZodOptional<z.ZodString>;
6
- }, "strip", z.ZodTypeAny, {
7
- clientId: string;
8
- clientSecret: string;
9
- scope?: string | undefined;
10
- }, {
11
- clientId: string;
12
- clientSecret: string;
13
- scope?: string | undefined;
14
- }>;
15
- export type GithubConfig = z.infer<typeof githubConfigGuard>;
16
- /**
17
- * This guard is used to validate the response from the GitHub API when requesting the user's email addresses.
18
- * Ref: https://docs.github.com/en/rest/users/emails?apiVersion=2022-11-28#list-email-addresses-for-the-authenticated-user
19
- */
20
- export declare const emailAddressGuard: z.ZodObject<{
21
- email: z.ZodString;
22
- primary: z.ZodBoolean;
23
- verified: z.ZodBoolean;
24
- visibility: z.ZodNullable<z.ZodString>;
25
- }, "strip", z.ZodTypeAny, {
26
- email: string;
27
- primary: boolean;
28
- verified: boolean;
29
- visibility: string | null;
30
- }, {
31
- email: string;
32
- primary: boolean;
33
- verified: boolean;
34
- visibility: string | null;
35
- }>;
36
- export declare const accessTokenResponseGuard: z.ZodObject<{
37
- access_token: z.ZodString;
38
- scope: z.ZodString;
39
- token_type: z.ZodString;
40
- }, "strip", z.ZodTypeAny, {
41
- scope: string;
42
- access_token: string;
43
- token_type: string;
44
- }, {
45
- scope: string;
46
- access_token: string;
47
- token_type: string;
48
- }>;
49
- export type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;
50
- export declare const userInfoResponseGuard: z.ZodObject<{
51
- id: z.ZodNumber;
52
- avatar_url: z.ZodNullable<z.ZodOptional<z.ZodString>>;
53
- email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
54
- name: z.ZodNullable<z.ZodOptional<z.ZodString>>;
55
- }, "strip", z.ZodTypeAny, {
56
- id: number;
57
- avatar_url?: string | null | undefined;
58
- email?: string | null | undefined;
59
- name?: string | null | undefined;
60
- }, {
61
- id: number;
62
- avatar_url?: string | null | undefined;
63
- email?: string | null | undefined;
64
- name?: string | null | undefined;
65
- }>;
66
- export type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;
67
- export declare const authorizationCallbackErrorGuard: z.ZodObject<{
68
- error: z.ZodString;
69
- error_description: z.ZodString;
70
- error_uri: z.ZodString;
71
- }, "strip", z.ZodTypeAny, {
72
- error: string;
73
- error_description: string;
74
- error_uri: string;
75
- }, {
76
- error: string;
77
- error_description: string;
78
- error_uri: string;
79
- }>;
80
- export declare const authResponseGuard: z.ZodObject<{
81
- code: z.ZodString;
82
- }, "strip", z.ZodTypeAny, {
83
- code: string;
84
- }, {
85
- code: string;
86
- }>;