@octane-xplat/auth 0.9.0 → 0.11.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 +115 -16
- package/dist/native/auth/src/base64.js +17 -0
- package/dist/native/auth/src/hosted.js +252 -0
- package/dist/native/auth/src/sha256.js +141 -0
- package/dist/native/index.js +6 -5
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/platform/src/a11y.js +1 -0
- package/dist/native/platform/src/app-info.js +1 -0
- package/dist/native/platform/src/auth-session.js +138 -0
- package/dist/native/platform/src/breakpoints.tsrx.js +5 -0
- package/dist/native/platform/src/clipboard.js +1 -0
- package/dist/native/platform/src/connectivity.js +1 -0
- package/dist/native/platform/src/deep-links.js +1 -0
- package/dist/native/platform/src/device.js +3 -0
- package/dist/native/platform/src/index.js +17 -0
- package/dist/native/platform/src/lifecycle.tsrx.js +7 -0
- package/dist/native/platform/src/locale.js +3 -0
- package/dist/native/platform/src/open-url.js +1 -0
- package/dist/native/platform/src/random.js +38 -0
- package/dist/native/platform/src/safe-area.tsrx.js +4 -0
- package/dist/native/platform/src/screen.tsrx.js +4 -0
- package/dist/native/platform/src/storage.js +1 -0
- package/dist/native/platform/src/system-bars.js +1 -0
- package/dist/native/secure-storage/src/index.js +2 -0
- package/dist/native/secure-storage/src/secure-storage.js +17 -0
- package/dist/web/auth/src/base64.js +17 -0
- package/dist/web/auth/src/hosted.js +251 -0
- package/dist/web/auth/src/sha256.js +141 -0
- package/dist/web/index.js +6 -5
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/platform/src/app-info.web.js +3 -0
- package/dist/web/platform/src/auth-session.web.js +10 -0
- package/dist/web/platform/src/breakpoints.web.tsrx.js +7 -0
- package/dist/web/platform/src/clipboard.web.js +3 -0
- package/dist/web/platform/src/deep-links.web.js +3 -0
- package/dist/web/platform/src/device.web.js +2 -0
- package/dist/web/platform/src/host-bridge.web.js +18 -0
- package/dist/web/platform/src/host-protocol.js +73 -0
- package/dist/web/platform/src/host-runtime.web.js +76 -0
- package/dist/web/platform/src/index.web.js +13 -0
- package/dist/web/platform/src/lifecycle.web.tsrx.js +3 -0
- package/dist/web/platform/src/locale.web.js +10 -0
- package/dist/web/platform/src/random.web.js +21 -0
- package/dist/web/platform/src/safe-area.web.tsrx.js +3 -0
- package/dist/web/platform/src/screen.web.tsrx.js +3 -0
- package/dist/web/platform/src/webauthn.web.js +2 -0
- package/dist/web/secure-storage/src/secure-storage.web.js +21 -0
- package/package.json +11 -5
- package/src/AppleSignInButton.macos.tsrx +28 -30
- package/src/GoogleSignInButton.macos.tsrx +18 -4
- package/src/apple-button.macos.ts +75 -0
- package/src/apple.macos.ts +87 -130
- package/src/base64.ts +25 -0
- package/src/google.macos.ts +81 -14
- package/src/hosted.ts +392 -0
- package/src/index.macos.ts +17 -0
- package/src/index.ts +17 -0
- package/src/index.web.ts +17 -0
- package/src/sha256.ts +118 -0
- package/src/types.ts +216 -4
- package/types/generated/.tsrx-typegen-manifest.json +3 -0
- package/types/generated/base64.d.ts +1 -0
- package/types/generated/hosted.d.ts +6 -0
- package/types/generated/index.d.ts +2 -1
- package/types/generated/index.web.d.ts +2 -1
- package/types/generated/sha256.d.ts +4 -0
- package/types/generated/types.d.ts +200 -4
- package/types/index.macos.d.ts +40 -0
- /package/dist/native/{AppleSignInButton.tsrx.js → auth/src/AppleSignInButton.tsrx.js} +0 -0
- /package/dist/native/{GoogleSignInButton.tsrx.js → auth/src/GoogleSignInButton.tsrx.js} +0 -0
- /package/dist/native/{apple.js → auth/src/apple.js} +0 -0
- /package/dist/native/{google.js → auth/src/google.js} +0 -0
- /package/dist/web/{AppleSignInButton.web.tsrx.js → auth/src/AppleSignInButton.web.tsrx.js} +0 -0
- /package/dist/web/{GoogleSignInButton.web.tsrx.js → auth/src/GoogleSignInButton.web.tsrx.js} +0 -0
- /package/dist/web/{apple.web.js → auth/src/apple.web.js} +0 -0
- /package/dist/web/{google.web.js → auth/src/google.web.js} +0 -0
package/src/apple.macos.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// available since macOS 10.15). The `com.apple.developer.applesignin`
|
|
4
4
|
// entitlement must sit on the packaged app for the sheet to present;
|
|
5
5
|
// hosts without it still load the framework and surface the OS error.
|
|
6
|
+
import { sha256Hex } from './sha256'
|
|
6
7
|
import type {
|
|
7
8
|
AppleAuth,
|
|
8
9
|
AppleAuthConfig,
|
|
@@ -41,92 +42,11 @@ const SCOPE_MAP: Record<string, () => any> = {
|
|
|
41
42
|
function isCancel(error: unknown): boolean {
|
|
42
43
|
// ASAuthorizationError.canceled = 1001 (NSError.code).
|
|
43
44
|
return (
|
|
44
|
-
(error as any)?.
|
|
45
|
-
|
|
45
|
+
(error as any)?.domain === 'com.apple.AuthenticationServices.AuthorizationError' &&
|
|
46
|
+
Number((error as any)?.code) === 1001
|
|
46
47
|
)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function utf8Bytes(input: string): Uint8Array {
|
|
50
|
-
const data = NSString.stringWithString(input).dataUsingEncoding(4 /* NSUTF8StringEncoding */)
|
|
51
|
-
return new Uint8Array(interop.bufferFromData(data))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// FIPS 180-4 — the request nonce must be the hex SHA-256 of the app's nonce
|
|
55
|
-
// (same transform the iOS plugin applies via CommonCrypto, which has no
|
|
56
|
-
// node-api metadata). Kept local to stay dependency-free.
|
|
57
|
-
function sha256Hex(input: string): string {
|
|
58
|
-
const K = new Uint32Array([
|
|
59
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
60
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
61
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
62
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
63
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
64
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
65
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
66
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
67
|
-
])
|
|
68
|
-
|
|
69
|
-
const H = new Uint32Array([
|
|
70
|
-
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
71
|
-
])
|
|
72
|
-
|
|
73
|
-
const bytes = utf8Bytes(input)
|
|
74
|
-
const bitLen = bytes.length * 8
|
|
75
|
-
const padded = new Uint8Array((bytes.length + 9 + 63) & ~63)
|
|
76
|
-
padded.set(bytes)
|
|
77
|
-
padded[bytes.length] = 0x80
|
|
78
|
-
const dv = new DataView(padded.buffer)
|
|
79
|
-
dv.setUint32(padded.length - 8, Math.floor(bitLen / 0x100000000))
|
|
80
|
-
dv.setUint32(padded.length - 4, bitLen >>> 0)
|
|
81
|
-
const w = new Uint32Array(64)
|
|
82
|
-
for (let block = 0; block < padded.length; block += 64) {
|
|
83
|
-
for (let t = 0; t < 16; t++) {
|
|
84
|
-
w[t] = dv.getUint32(block + t * 4)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (let t = 16; t < 64; t++) {
|
|
88
|
-
const s0 =
|
|
89
|
-
((w[t - 15] >>> 7) | (w[t - 15] << 25)) ^
|
|
90
|
-
((w[t - 15] >>> 18) | (w[t - 15] << 14)) ^
|
|
91
|
-
(w[t - 15] >>> 3)
|
|
92
|
-
|
|
93
|
-
const s1 =
|
|
94
|
-
((w[t - 2] >>> 17) | (w[t - 2] << 15)) ^
|
|
95
|
-
((w[t - 2] >>> 19) | (w[t - 2] << 13)) ^
|
|
96
|
-
(w[t - 2] >>> 10)
|
|
97
|
-
|
|
98
|
-
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let [a, b, c, d, e, f, g, h] = H
|
|
102
|
-
for (let t = 0; t < 64; t++) {
|
|
103
|
-
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7))
|
|
104
|
-
const t1 = (h + S1 + ((e & f) ^ (~e & g)) + K[t] + w[t]) >>> 0
|
|
105
|
-
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10))
|
|
106
|
-
const t2 = (S0 + ((a & b) ^ (a & c) ^ (b & c))) >>> 0
|
|
107
|
-
h = g
|
|
108
|
-
g = f
|
|
109
|
-
f = e
|
|
110
|
-
e = (d + t1) >>> 0
|
|
111
|
-
d = c
|
|
112
|
-
c = b
|
|
113
|
-
b = a
|
|
114
|
-
a = (t1 + t2) >>> 0
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
H[0] = (H[0] + a) >>> 0
|
|
118
|
-
H[1] = (H[1] + b) >>> 0
|
|
119
|
-
H[2] = (H[2] + c) >>> 0
|
|
120
|
-
H[3] = (H[3] + d) >>> 0
|
|
121
|
-
H[4] = (H[4] + e) >>> 0
|
|
122
|
-
H[5] = (H[5] + f) >>> 0
|
|
123
|
-
H[6] = (H[6] + g) >>> 0
|
|
124
|
-
H[7] = (H[7] + h) >>> 0
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return [...H].map((x) => x.toString(16).padStart(8, '0')).join('')
|
|
128
|
-
}
|
|
129
|
-
|
|
130
50
|
function toCredential(credential: any): AuthCredential {
|
|
131
51
|
const tokenData = credential.valueForKey('identityToken')
|
|
132
52
|
const codeData = credential.valueForKey('authorizationCode')
|
|
@@ -189,15 +109,24 @@ function ensureClasses() {
|
|
|
189
109
|
|
|
190
110
|
AppleAuthDelegate = NSObject.extend(
|
|
191
111
|
{
|
|
192
|
-
authorizationControllerDidCompleteWithAuthorization(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
112
|
+
authorizationControllerDidCompleteWithAuthorization(
|
|
113
|
+
this: any,
|
|
114
|
+
_controller: any,
|
|
115
|
+
authorization: any,
|
|
116
|
+
) {
|
|
117
|
+
try {
|
|
118
|
+
const credential = toCredential(authorization.credential)
|
|
119
|
+
if (!credential.user.id || !credential.idToken) {
|
|
120
|
+
throw new Error('Sign in with Apple returned an incomplete credential')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.finish?.({ status: 'success', credential })
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.finish?.({ status: 'error', message: String((error as any)?.message ?? error) })
|
|
126
|
+
}
|
|
196
127
|
},
|
|
197
|
-
authorizationControllerDidCompleteWithError(_controller: any, error: any) {
|
|
198
|
-
|
|
199
|
-
activeFinish = null
|
|
200
|
-
finish?.(
|
|
128
|
+
authorizationControllerDidCompleteWithError(this: any, _controller: any, error: any) {
|
|
129
|
+
this.finish?.(
|
|
201
130
|
isCancel(error)
|
|
202
131
|
? { status: 'cancelled' }
|
|
203
132
|
: { status: 'error', message: String((error as any)?.localizedDescription ?? error) },
|
|
@@ -243,7 +172,7 @@ function ensureClasses() {
|
|
|
243
172
|
let activeController: any = null
|
|
244
173
|
let activeDelegate: any = null
|
|
245
174
|
let activeAnchor: any = null
|
|
246
|
-
let
|
|
175
|
+
let active = false
|
|
247
176
|
|
|
248
177
|
export const appleAuth: AppleAuth = {
|
|
249
178
|
get supported() {
|
|
@@ -257,37 +186,59 @@ export const appleAuth: AppleAuth = {
|
|
|
257
186
|
return { status: 'error', message: 'AuthenticationServices is unavailable on this host' }
|
|
258
187
|
}
|
|
259
188
|
|
|
260
|
-
if (
|
|
189
|
+
if (active) {
|
|
261
190
|
return { status: 'error', message: 'a Sign in with Apple flow is already active' }
|
|
262
191
|
}
|
|
263
192
|
|
|
193
|
+
active = true
|
|
264
194
|
return new Promise<SignInResult>((resolve) => {
|
|
195
|
+
let settled = false
|
|
265
196
|
const finish = (result: SignInResult) => {
|
|
197
|
+
if (settled) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
settled = true
|
|
202
|
+
active = false
|
|
266
203
|
activeController = null
|
|
267
204
|
activeDelegate = null
|
|
268
205
|
activeAnchor = null
|
|
269
|
-
activeFinish = null
|
|
270
206
|
resolve(result)
|
|
271
207
|
}
|
|
272
208
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
request
|
|
209
|
+
try {
|
|
210
|
+
const app = NSApplication.sharedApplication
|
|
211
|
+
if (!(app.keyWindow ?? app.mainWindow ?? app.windows?.firstObject)) {
|
|
212
|
+
throw new Error('Sign in with Apple requires an AppKit presentation window')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ensureClasses()
|
|
216
|
+
activeDelegate = AppleAuthDelegate.new()
|
|
217
|
+
activeDelegate.finish = finish
|
|
218
|
+
const provider = ASAuthorizationAppleIDProvider.new()
|
|
219
|
+
const request = provider.createRequest()
|
|
220
|
+
if (options?.scopes?.length) {
|
|
221
|
+
request.requestedScopes = options.scopes.map((s) => SCOPE_MAP[s]?.()).filter(Boolean)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (options?.nonce !== undefined) {
|
|
225
|
+
request.nonce = sha256Hex(options.nonce)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
activeAnchor = AppleAuthAnchor.new()
|
|
229
|
+
activeController = ASAuthorizationController.alloc().initWithAuthorizationRequests([
|
|
230
|
+
request,
|
|
231
|
+
])
|
|
232
|
+
|
|
233
|
+
activeController.delegate = activeDelegate
|
|
234
|
+
activeController.presentationContextProvider = activeAnchor
|
|
235
|
+
activeController.performRequests()
|
|
236
|
+
} catch (error) {
|
|
237
|
+
finish({
|
|
238
|
+
status: 'error',
|
|
239
|
+
message: String((error as any)?.localizedDescription ?? (error as any)?.message ?? error),
|
|
240
|
+
})
|
|
284
241
|
}
|
|
285
|
-
|
|
286
|
-
activeAnchor = AppleAuthAnchor.new()
|
|
287
|
-
activeController = ASAuthorizationController.alloc().initWithAuthorizationRequests([request])
|
|
288
|
-
activeController.delegate = activeDelegate
|
|
289
|
-
activeController.presentationContextProvider = activeAnchor
|
|
290
|
-
activeController.performRequests()
|
|
291
242
|
})
|
|
292
243
|
},
|
|
293
244
|
async getCredentialState(userId: string): Promise<AppleCredentialState> {
|
|
@@ -296,25 +247,31 @@ export const appleAuth: AppleAuth = {
|
|
|
296
247
|
}
|
|
297
248
|
|
|
298
249
|
return new Promise<AppleCredentialState>((resolve) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
250
|
+
try {
|
|
251
|
+
ASAuthorizationAppleIDProvider.new().getCredentialStateForUserIDCompletion(
|
|
252
|
+
userId,
|
|
253
|
+
(state: number, error: any) => {
|
|
254
|
+
if (error) {
|
|
255
|
+
resolve('unknown')
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
resolve(
|
|
260
|
+
state === 1
|
|
261
|
+
? 'authorized'
|
|
262
|
+
: state === 0
|
|
263
|
+
? 'revoked'
|
|
264
|
+
: state === 3
|
|
265
|
+
? 'transferred'
|
|
266
|
+
: state === 2
|
|
267
|
+
? 'notFound'
|
|
268
|
+
: 'unknown',
|
|
269
|
+
)
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
} catch {
|
|
273
|
+
resolve('unknown')
|
|
274
|
+
}
|
|
318
275
|
})
|
|
319
276
|
},
|
|
320
277
|
}
|
package/src/base64.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Unpadded base64url (RFC 4648 §5) — the encoding PKCE and hosted-auth
|
|
2
|
+
// wire formats use. `btoa` is not declared on every target's typecheck,
|
|
3
|
+
// so encode by hand.
|
|
4
|
+
|
|
5
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
|
6
|
+
|
|
7
|
+
export function bytesToBase64Url(bytes: Uint8Array): string {
|
|
8
|
+
let out = ''
|
|
9
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
10
|
+
const a = bytes[i]
|
|
11
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0
|
|
12
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0
|
|
13
|
+
out += ALPHABET[a >> 2]
|
|
14
|
+
out += ALPHABET[((a & 0x03) << 4) | (b >> 4)]
|
|
15
|
+
if (i + 1 < bytes.length) {
|
|
16
|
+
out += ALPHABET[((b & 0x0f) << 2) | (c >> 6)]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (i + 2 < bytes.length) {
|
|
20
|
+
out += ALPHABET[c & 0x3f]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return out
|
|
25
|
+
}
|
package/src/google.macos.ts
CHANGED
|
@@ -1,18 +1,85 @@
|
|
|
1
|
-
// Google
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import type { GoogleAuth, SignInResult } from './types'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
message: 'Google Sign-In is unsupported by the AppKit host — use a hosted authSession flow',
|
|
9
|
-
}
|
|
1
|
+
// Google credentials on AppKit come from the app's hosted OAuth backend.
|
|
2
|
+
// The shared methods/result stay identical to the SDK leaves.
|
|
3
|
+
import { authSession } from '@octane-xplat/platform'
|
|
4
|
+
import type { GoogleAuth, GoogleAuthConfig, SignInResult } from './types'
|
|
5
|
+
|
|
6
|
+
let configuration: GoogleAuthConfig | undefined
|
|
7
|
+
let active = false
|
|
10
8
|
|
|
11
9
|
export const googleAuth: GoogleAuth = {
|
|
12
|
-
supported
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
get supported() {
|
|
11
|
+
return authSession.supported
|
|
12
|
+
},
|
|
13
|
+
async configure(config) {
|
|
14
|
+
configuration = config ? { ...config, scopes: config.scopes?.slice() } : undefined
|
|
15
|
+
},
|
|
16
|
+
async signIn(options = {}): Promise<SignInResult> {
|
|
17
|
+
const config = configuration
|
|
18
|
+
const flow = config?.hostedFlow
|
|
19
|
+
const session = authSession.impl
|
|
20
|
+
if (!session) {
|
|
21
|
+
return { status: 'error', message: 'AuthenticationServices is unavailable on this host' }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!flow) {
|
|
25
|
+
return {
|
|
26
|
+
status: 'error',
|
|
27
|
+
message: 'Google Sign-In on macOS requires configure({ hostedFlow })',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (active) {
|
|
32
|
+
return { status: 'error', message: 'a Google Sign-In flow is already active' }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
active = true
|
|
36
|
+
try {
|
|
37
|
+
const request = await flow.createRequest({
|
|
38
|
+
...options,
|
|
39
|
+
clientId: config?.clientId,
|
|
40
|
+
serverClientId: config?.serverClientId,
|
|
41
|
+
scopes: config?.scopes?.slice(),
|
|
42
|
+
hostedDomain: config?.hostedDomain,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Backend URLs must use TLS; the backend owns Google registration,
|
|
46
|
+
// state/nonce binding and single-use callback-code redemption.
|
|
47
|
+
if (!/^https:\/\//i.test(request.url)) {
|
|
48
|
+
throw new Error('Google hosted sign-in requires an HTTPS URL')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await session.open(request.url, {
|
|
52
|
+
callbackScheme: request.callbackScheme,
|
|
53
|
+
prefersEphemeralSession: true,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if (result.type === 'cancel') {
|
|
57
|
+
return { status: 'cancelled' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.type === 'error') {
|
|
61
|
+
return { status: 'error', message: result.message }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const credential = await flow.complete(result.url)
|
|
65
|
+
if (
|
|
66
|
+
credential?.provider !== 'google' ||
|
|
67
|
+
typeof credential.user?.id !== 'string' ||
|
|
68
|
+
!credential.user.id ||
|
|
69
|
+
!Array.isArray(credential.scopes) ||
|
|
70
|
+
credential.scopes.some((scope) => typeof scope !== 'string')
|
|
71
|
+
) {
|
|
72
|
+
throw new Error('Google hosted sign-in returned an invalid credential')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { status: 'success', credential }
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return { status: 'error', message: String((error as any)?.message ?? error) }
|
|
78
|
+
} finally {
|
|
79
|
+
active = false
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async signOut() {
|
|
83
|
+
await configuration?.hostedFlow?.signOut?.()
|
|
16
84
|
},
|
|
17
|
-
async signOut() {},
|
|
18
85
|
}
|