@authon/react-native-wallet 0.4.0 → 0.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/CHANGELOG.md +4 -0
- package/README.md +25 -1
- package/build/appleSignIn.d.ts +26 -0
- package/build/appleSignIn.d.ts.map +1 -0
- package/build/appleSignIn.js +76 -0
- package/build/appleSignIn.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/native.d.ts +6 -0
- package/build/native.d.ts.map +1 -1
- package/build/native.js.map +1 -1
- package/ios/AuthonReactNativeWallet.podspec +3 -1
- package/ios/AuthonWalletModule.swift +26 -0
- package/ios/vendor/AuthonAppleSignIn/AppleSignIn.swift +285 -0
- package/ios/vendor/GENERATED.md +1 -1
- package/package.json +1 -1
- package/src/appleSignIn.ts +94 -0
- package/src/index.ts +2 -0
- package/src/native.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @authon/react-native-wallet
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
- `signInWithAppleNative(client, { appId? })` (iOS): native Sign in with Apple through the system sheet, with no browser or web page, using the server's single-use start/complete flow. JS receives only the final session or MFA challenge, which `client.adoptSignInResult()` (@authon/react-native 0.9.4) makes the app session. Apple's identity token, authorization code, nonce and verifier stay in Swift. Errors carry the shared sign-in codes (`OAUTH_CANCELLED`, `OAUTH_REDIRECT_NOT_ALLOWED` with `serverCode` APP_NOT_REGISTERED / APP_NOT_APPROVED, `OAUTH_STATE_MISMATCH`, `OAUTH_EXCHANGE_FAILED`, ...) and `diagnosticId`; cancel and errors never fall back to a web flow. Android throws `UNSUPPORTED_PLATFORM` (use `startOAuth('apple')`).
|
|
6
|
+
|
|
3
7
|
## 0.4.0
|
|
4
8
|
|
|
5
9
|
- `signChainTransaction({ chain, account, address, payload })` for app-built transactions on other chains (NATIVE.md section 12): Solana (`{ message }` → `{ signature }`), TRON (`{ rawDataHex }` → `{ txID, signature }`), Bitcoin and Bitcoin testnet (`{ psbt }` → `{ psbt, txHex? }`) and XRP (`{ txBlobHex }` → `{ signature }`). EVM stays on `signTransaction` (`INVALID_PARAMS` for `chain: 'evm'`). The native approval UI summarizes the transaction before the key is assembled.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> (`AuthonWallet`) and Kotlin (`dev.authon.sdk.wallet`) wallet implementations.
|
|
5
5
|
|
|
6
6
|
[](../../LICENSE)
|
|
7
|
-
[]()
|
|
8
8
|
|
|
9
9
|
It is the same wallet as the web SDK (`@authon/js`): same wallet row, same `/v1/wallet/me/*` API,
|
|
10
10
|
same shares. A user gets the same address in your web app and your mobile app, and the app install
|
|
@@ -186,6 +186,30 @@ ignored).
|
|
|
186
186
|
| `deviceLock` | `DEVICE_LOCK_NOT_SET` | `CHECK_FAILED` |
|
|
187
187
|
| `passkeyBackup` | `OS_UNSUPPORTED` (iOS < 18, Android < 9), `PASSKEY_API_UNAVAILABLE` | `PRF_PROVIDER_UNVERIFIED` (the provider's PRF support is confirmed on the first `protect()`) |
|
|
188
188
|
|
|
189
|
+
### Native Sign in with Apple (iOS)
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import { useAuthon, AuthonAuthError, AuthonMfaRequiredError } from '@authon/react-native';
|
|
193
|
+
import { signInWithAppleNative } from '@authon/react-native-wallet';
|
|
194
|
+
|
|
195
|
+
const { client } = useAuthon();
|
|
196
|
+
try {
|
|
197
|
+
await signInWithAppleNative(client); // system Apple sheet, then the session is the app session
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (e instanceof AuthonMfaRequiredError) return showMfa(e.mfaToken); // then verifyMfa()
|
|
200
|
+
if (e instanceof AuthonAuthError && e.code === 'OAUTH_CANCELLED') return; // show nothing
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The app needs the "Sign in with Apple" capability (`ios.entitlements` `com.apple.developer.applesignin: ["Default"]` in app.json, or `expo-apple-authentication`) and must be
|
|
206
|
+
registered and approved in the Authon console (`TEAMID.bundleId`). JS never receives Apple's identity
|
|
207
|
+
token, authorization code, nonce or the PKCE verifier. Codes: `OAUTH_CANCELLED`,
|
|
208
|
+
`OAUTH_REDIRECT_NOT_ALLOWED` (`serverCode` `APP_NOT_REGISTERED` / `APP_NOT_APPROVED`),
|
|
209
|
+
`OAUTH_STATE_MISMATCH`, `OAUTH_EXCHANGE_FAILED`, `ACCOUNT_LINK_REQUIRED`, `NETWORK_ERROR`,
|
|
210
|
+
`RATE_LIMITED`, `API_ERROR`, with `diagnosticId`. There is no web fallback. On Android it throws
|
|
211
|
+
`UNSUPPORTED_PLATFORM`; use `startOAuth('apple')` there.
|
|
212
|
+
|
|
189
213
|
### Errors
|
|
190
214
|
|
|
191
215
|
Every failure is an `AuthonWalletError` with `code`, `message`, `diagnosticId` (the server's request ID
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The part of @authon/react-native's AuthonMobileClient this helper uses. */
|
|
2
|
+
export interface AppleSignInClient {
|
|
3
|
+
getApiUrl(): string;
|
|
4
|
+
getPublishableKey(): string;
|
|
5
|
+
adoptSignInResult(result: never): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface SignInWithAppleNativeOptions {
|
|
8
|
+
/** `TEAMID.bundleId` as registered in the Authon console; detected from the app when omitted. */
|
|
9
|
+
appId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Native Sign in with Apple on iOS: shows the system Apple sheet right away (no browser, no web page)
|
|
13
|
+
* and makes the resulting session the session of `client` (an AuthonMobileClient from
|
|
14
|
+
* @authon/react-native, e.g. `useAuthon().client`), like a completed OAuth sign-in.
|
|
15
|
+
*
|
|
16
|
+
* JS never sees Apple's identity token, authorization code, nonce or the PKCE verifier; only the
|
|
17
|
+
* final result reaches JS. Errors are AuthonAuthError with the shared sign-in codes: OAUTH_CANCELLED
|
|
18
|
+
* (the user closed the sheet, show nothing), OAUTH_REDIRECT_NOT_ALLOWED (app not registered or not
|
|
19
|
+
* approved; `serverCode` tells APP_NOT_REGISTERED from APP_NOT_APPROVED), OAUTH_STATE_MISMATCH
|
|
20
|
+
* (server OAUTH_STATE_INVALID), OAUTH_EXCHANGE_FAILED, ACCOUNT_LINK_REQUIRED, NETWORK_ERROR,
|
|
21
|
+
* RATE_LIMITED, API_ERROR, with `diagnosticId` when the server answered.
|
|
22
|
+
* MFA: throws AuthonMfaRequiredError, finish with verifyMfa(). Cancel and errors never fall back to
|
|
23
|
+
* a web flow. Android: UNSUPPORTED_PLATFORM, use the normal OAuth flow (`startOAuth('apple')`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function signInWithAppleNative(client: AppleSignInClient, options?: SignInWithAppleNativeOptions): Promise<unknown>;
|
|
26
|
+
//# sourceMappingURL=appleSignIn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appleSignIn.d.ts","sourceRoot":"","sources":["../src/appleSignIn.ts"],"names":[],"mappings":"AAKA,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,SAAS,IAAI,MAAM,CAAC;IACpB,iBAAiB,IAAI,MAAM,CAAC;IAC5B,iBAAiB,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAiCD,MAAM,WAAW,4BAA4B;IAC3C,iGAAiG;IACjG,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BnI"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { getNativeModule } from './native';
|
|
3
|
+
/** Fallback with the same shape as @authon/react-native's AuthonAuthError. */
|
|
4
|
+
class AppleSignInError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
diagnosticId;
|
|
7
|
+
retryable;
|
|
8
|
+
constructor(code, message, options = {}) {
|
|
9
|
+
super(message ?? code);
|
|
10
|
+
this.name = 'AuthonAuthError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.diagnosticId = options.diagnosticId ?? null;
|
|
13
|
+
this.retryable = options.retryable ?? false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function authError(code, message, options = {}) {
|
|
17
|
+
let Ctor = AppleSignInError;
|
|
18
|
+
try {
|
|
19
|
+
const mod = require('@authon/react-native');
|
|
20
|
+
if (mod?.AuthonAuthError)
|
|
21
|
+
Ctor = mod.AuthonAuthError;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// @authon/react-native not installed: same fields, local class.
|
|
25
|
+
}
|
|
26
|
+
return new Ctor(code, message, options);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Native Sign in with Apple on iOS: shows the system Apple sheet right away (no browser, no web page)
|
|
30
|
+
* and makes the resulting session the session of `client` (an AuthonMobileClient from
|
|
31
|
+
* @authon/react-native, e.g. `useAuthon().client`), like a completed OAuth sign-in.
|
|
32
|
+
*
|
|
33
|
+
* JS never sees Apple's identity token, authorization code, nonce or the PKCE verifier; only the
|
|
34
|
+
* final result reaches JS. Errors are AuthonAuthError with the shared sign-in codes: OAUTH_CANCELLED
|
|
35
|
+
* (the user closed the sheet, show nothing), OAUTH_REDIRECT_NOT_ALLOWED (app not registered or not
|
|
36
|
+
* approved; `serverCode` tells APP_NOT_REGISTERED from APP_NOT_APPROVED), OAUTH_STATE_MISMATCH
|
|
37
|
+
* (server OAUTH_STATE_INVALID), OAUTH_EXCHANGE_FAILED, ACCOUNT_LINK_REQUIRED, NETWORK_ERROR,
|
|
38
|
+
* RATE_LIMITED, API_ERROR, with `diagnosticId` when the server answered.
|
|
39
|
+
* MFA: throws AuthonMfaRequiredError, finish with verifyMfa(). Cancel and errors never fall back to
|
|
40
|
+
* a web flow. Android: UNSUPPORTED_PLATFORM, use the normal OAuth flow (`startOAuth('apple')`).
|
|
41
|
+
*/
|
|
42
|
+
export async function signInWithAppleNative(client, options = {}) {
|
|
43
|
+
if (Platform.OS !== 'ios') {
|
|
44
|
+
throw authError('UNSUPPORTED_PLATFORM', 'Native Sign in with Apple is iOS only; on Android use startOAuth("apple")');
|
|
45
|
+
}
|
|
46
|
+
const native = getNativeModule();
|
|
47
|
+
if (!native?.signInWithApple) {
|
|
48
|
+
throw authError('NOT_CONFIGURED', '@authon/react-native-wallet native module not found. Use a development build and rebuild the app.');
|
|
49
|
+
}
|
|
50
|
+
const result = await native.signInWithApple({
|
|
51
|
+
publishableKey: client.getPublishableKey(),
|
|
52
|
+
apiUrl: client.getApiUrl(),
|
|
53
|
+
appId: options.appId ?? null,
|
|
54
|
+
});
|
|
55
|
+
if (!result || typeof result !== 'object')
|
|
56
|
+
throw authError('API_ERROR', 'Apple sign-in returned no result');
|
|
57
|
+
if (result.ok !== true) {
|
|
58
|
+
const code = typeof result.code === 'string' && result.code ? result.code : 'API_ERROR';
|
|
59
|
+
const error = authError(code, typeof result.message === 'string' && result.message ? result.message : code, {
|
|
60
|
+
diagnosticId: typeof result.diagnosticId === 'string' && result.diagnosticId ? result.diagnosticId : null,
|
|
61
|
+
retryable: result.retryable === true,
|
|
62
|
+
});
|
|
63
|
+
// The server's own code (APP_NOT_REGISTERED, APP_NOT_APPROVED, OAUTH_STATE_INVALID, ...), when sent.
|
|
64
|
+
const serverCode = result.serverCode;
|
|
65
|
+
throw Object.assign(error, { serverCode: typeof serverCode === 'string' && serverCode ? serverCode : null });
|
|
66
|
+
}
|
|
67
|
+
let session;
|
|
68
|
+
try {
|
|
69
|
+
session = typeof result.value === 'string' ? JSON.parse(result.value) : result.value;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw authError('API_ERROR', 'Apple sign-in returned an unreadable result');
|
|
73
|
+
}
|
|
74
|
+
return client.adoptSignInResult(session);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=appleSignIn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appleSignIn.js","sourceRoot":"","sources":["../src/appleSignIn.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { AuthonWalletClient, createAuthonWallet } from './client';
|
|
|
2
2
|
export { AuthonWalletError } from './errors';
|
|
3
3
|
export type { WalletErrorCode } from './errors';
|
|
4
4
|
export { useAuthonWallet } from './useAuthonWallet';
|
|
5
|
+
export { signInWithAppleNative } from './appleSignIn';
|
|
6
|
+
export type { AppleSignInClient, SignInWithAppleNativeOptions } from './appleSignIn';
|
|
5
7
|
export type { UseAuthonWalletOptions, UseAuthonWalletResult } from './useAuthonWallet';
|
|
6
8
|
export type { AuthonWalletOptions, CapabilityCheck, CapabilityStatus, WalletCapabilities, WalletChain, WalletChainSignRequest, EIP1193Provider, EIP1193RequestArguments, EmbeddedWallet, EmbeddedWalletDevice, Hex, TokenGetter, TypedDataPayload, WalletAccount, WalletAppConfig, WalletChangeListener, WalletMessage, WalletSignOptions, WalletTransactionRequest, } from './types';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACvF,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,cAAc,EACd,oBAAoB,EACpB,GAAG,EACH,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACrF,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACvF,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,cAAc,EACd,oBAAoB,EACpB,GAAG,EACH,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,SAAS,CAAC"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
package/build/native.d.ts
CHANGED
|
@@ -36,6 +36,12 @@ export interface AuthonWalletNativeModule {
|
|
|
36
36
|
upgradeBackup(id: string, token: string): Promise<NativeResult<unknown>>;
|
|
37
37
|
restore(id: string, token: string, reset: boolean): Promise<NativeResult<unknown>>;
|
|
38
38
|
revokeDevice(id: string, token: string, deviceId: string): Promise<NativeResult<unknown>>;
|
|
39
|
+
/** iOS only: native Sign in with Apple; resolves the server's sign-in result (JSON), never Apple's tokens. */
|
|
40
|
+
signInWithApple?(config: {
|
|
41
|
+
publishableKey: string;
|
|
42
|
+
apiUrl: string;
|
|
43
|
+
appId: string | null;
|
|
44
|
+
}): Promise<NativeResult<unknown>>;
|
|
39
45
|
}
|
|
40
46
|
export declare function getNativeModule(): AuthonWalletNativeModule | null;
|
|
41
47
|
/** Tests only: replaces the native module (pass undefined to restore). */
|
package/build/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAEA,uEAAuE;AACvE,MAAM,MAAM,YAAY,CAAC,CAAC,IACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAErG,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2BAA2B,EAAE,OAAO,CAAC;IACrC,OAAO,EAAE;QAAE,iBAAiB,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;CAC/D;AAED,iGAAiG;AACjG,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACjH,WAAW,CACT,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GAAG,KAAK,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAClC,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACzI,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7I,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACtJ,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACnF,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
|
package/build/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AA2ChE,IAAI,QAAqD,CAAC;AAE1D,MAAM,UAAU,eAAe;IAC7B,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,OAAO,2BAA2B,CAA2B,cAAc,CAAC,CAAC;AAC/E,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,yBAAyB,CAAC,MAAmD;IAC3F,QAAQ,GAAG,MAAM,CAAC;AACpB,CAAC"}
|
|
@@ -17,12 +17,14 @@ Pod::Spec.new do |s|
|
|
|
17
17
|
s.dependency 'ExpoModulesCore'
|
|
18
18
|
s.frameworks = 'Security', 'CryptoKit', 'LocalAuthentication', 'AuthenticationServices', 'SwiftUI'
|
|
19
19
|
|
|
20
|
-
# vendor/ is generated from swift/Sources by scripts/sync-native-wallet.mjs
|
|
20
|
+
# vendor/ is generated from swift/Sources by scripts/sync-native-wallet.mjs (wallet core and the
|
|
21
|
+
# standalone native Sign in with Apple).
|
|
21
22
|
# CSecp256k1: compile only build/*.c (they include src/*.c); CKeccak: sha3.c, ripemd160.c and memzero.c;
|
|
22
23
|
# CMonocypher (Ed25519, deterministic like the web wallet): both .c files, no flags.
|
|
23
24
|
s.source_files = [
|
|
24
25
|
'*.swift',
|
|
25
26
|
'vendor/AuthonWallet/**/*.swift',
|
|
27
|
+
'vendor/AuthonAppleSignIn/*.swift',
|
|
26
28
|
'vendor/CSecp256k1/build/*.c',
|
|
27
29
|
'vendor/CSecp256k1/include/*.h',
|
|
28
30
|
'vendor/CKeccak/sha3.c',
|
|
@@ -18,6 +18,12 @@ public final class AuthonWalletModule: Module {
|
|
|
18
18
|
self.envelope { try self.configure(id, config); return NSNull() }
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Native Sign in with Apple. Resolves the API's sign-in result as JSON (a session or an MFA
|
|
22
|
+
// challenge); Apple's identity token, authorization code, nonce and verifier stay in Swift.
|
|
23
|
+
AsyncFunction("signInWithApple") { (config: [String: Any]) async -> [String: Any] in
|
|
24
|
+
await Self.signInWithApple(config)
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
AsyncFunction("release") { (id: String) -> [String: Any] in
|
|
22
28
|
self.lock.withLock { self.wallets[id] = nil }
|
|
23
29
|
return ["ok": true, "value": NSNull()]
|
|
@@ -153,6 +159,26 @@ public final class AuthonWalletModule: Module {
|
|
|
153
159
|
}
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
private static func signInWithApple(_ config: [String: Any]) async -> [String: Any] {
|
|
163
|
+
guard let publishableKey = config["publishableKey"] as? String, !publishableKey.isEmpty else {
|
|
164
|
+
return ["ok": false, "code": "OAUTH_START_FAILED", "message": "publishableKey required", "diagnosticId": NSNull(), "retryable": false]
|
|
165
|
+
}
|
|
166
|
+
let apiUrl = (config["apiUrl"] as? String).flatMap { $0.isEmpty ? nil : $0 } ?? "https://api.authon.dev"
|
|
167
|
+
let appId = (config["appId"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
168
|
+
do {
|
|
169
|
+
let result = try await AuthonAppleSignIn(publishableKey: publishableKey, apiURL: apiUrl, appId: appId).signIn()
|
|
170
|
+
let data = try JSONSerialization.data(withJSONObject: result)
|
|
171
|
+
return ["ok": true, "value": String(decoding: data, as: UTF8.self)]
|
|
172
|
+
} catch let e as AppleSignInError {
|
|
173
|
+
return [
|
|
174
|
+
"ok": false, "code": e.code, "message": e.message, "diagnosticId": orNull(e.diagnosticId),
|
|
175
|
+
"serverCode": orNull(e.serverCode), "retryable": e.retryable,
|
|
176
|
+
]
|
|
177
|
+
} catch {
|
|
178
|
+
return ["ok": false, "code": "API_ERROR", "message": "unexpected error", "diagnosticId": NSNull(), "retryable": true]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
156
182
|
private static func failure(_ error: Error) -> [String: Any] {
|
|
157
183
|
let e: WalletError
|
|
158
184
|
if let w = error as? WalletError {
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonAppleSignIn/AppleSignIn.swift. Do not edit: change the source and run the script.
|
|
2
|
+
import AuthenticationServices
|
|
3
|
+
import CryptoKit
|
|
4
|
+
import Foundation
|
|
5
|
+
import Security
|
|
6
|
+
#if canImport(UIKit)
|
|
7
|
+
import UIKit
|
|
8
|
+
#elseif canImport(AppKit)
|
|
9
|
+
import AppKit
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
/// Error of the native Apple sign-in. `code` is a sign-in code shared by every Authon SDK
|
|
13
|
+
/// (docs/wallet/NATIVE.md section 10): OAUTH_CANCELLED (show nothing), OAUTH_START_FAILED,
|
|
14
|
+
/// OAUTH_REDIRECT_NOT_ALLOWED (app not registered or approved), OAUTH_PROVIDER_ERROR,
|
|
15
|
+
/// OAUTH_STATE_MISMATCH, OAUTH_EXCHANGE_FAILED, ACCOUNT_LINK_REQUIRED, NETWORK_ERROR,
|
|
16
|
+
/// RATE_LIMITED, API_ERROR.
|
|
17
|
+
public struct AppleSignInError: Error, Equatable, LocalizedError, Sendable {
|
|
18
|
+
public let code: String
|
|
19
|
+
public let message: String
|
|
20
|
+
/// The server's request ID when the error came from an API response.
|
|
21
|
+
public let diagnosticId: String?
|
|
22
|
+
/// The server's own `code`, when it sent one (e.g. APP_NOT_APPROVED).
|
|
23
|
+
public let serverCode: String?
|
|
24
|
+
public let statusCode: Int
|
|
25
|
+
/// Whether the same call can succeed again. False for Apple ID sheet failures: showing the
|
|
26
|
+
/// same sheet again does not help (e.g. no Apple account on the device).
|
|
27
|
+
public let retryable: Bool
|
|
28
|
+
|
|
29
|
+
public var errorDescription: String? { message }
|
|
30
|
+
|
|
31
|
+
init(_ code: String, _ message: String, diagnosticId: String? = nil, serverCode: String? = nil, statusCode: Int = 0, retryable: Bool? = nil) {
|
|
32
|
+
self.code = code
|
|
33
|
+
self.message = message
|
|
34
|
+
self.diagnosticId = diagnosticId
|
|
35
|
+
self.serverCode = serverCode
|
|
36
|
+
self.statusCode = statusCode
|
|
37
|
+
self.retryable = retryable ?? ["OAUTH_START_FAILED", "OAUTH_PROVIDER_ERROR", "OAUTH_STATE_MISMATCH", "OAUTH_EXCHANGE_FAILED", "NETWORK_ERROR", "RATE_LIMITED", "API_ERROR"].contains(code)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// What the Apple ID sheet returned. Credentials only travel to the Authon API.
|
|
42
|
+
public struct AppleIDResult: Sendable {
|
|
43
|
+
public var identityToken: Data?
|
|
44
|
+
public var authorizationCode: Data?
|
|
45
|
+
public var fullName: PersonNameComponents?
|
|
46
|
+
|
|
47
|
+
public init(identityToken: Data?, authorizationCode: Data?, fullName: PersonNameComponents?) {
|
|
48
|
+
self.identityToken = identityToken
|
|
49
|
+
self.authorizationCode = authorizationCode
|
|
50
|
+
self.fullName = fullName
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Shows the Apple ID sheet for a nonce (already hashed: lowercase hex SHA-256 of the server nonce).
|
|
55
|
+
public protocol AppleIDAuthorizing: Sendable {
|
|
56
|
+
func authorize(hashedNonce: String) async throws -> AppleIDResult
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Native Sign in with Apple against Authon's nonce-bound flow:
|
|
60
|
+
/// `POST /v1/auth/oauth/native/apple/start` → Apple ID sheet → `POST /v1/auth/oauth/native/apple/complete`.
|
|
61
|
+
/// There is no browser and no web fallback. An attempt is used once: after any failure, start again.
|
|
62
|
+
/// The identity token, authorization code, nonce and verifier are never logged or returned.
|
|
63
|
+
public final class AuthonAppleSignIn: @unchecked Sendable {
|
|
64
|
+
private let base: String
|
|
65
|
+
private let publishableKey: String
|
|
66
|
+
private let appId: String?
|
|
67
|
+
private let authorizer: AppleIDAuthorizing
|
|
68
|
+
private let session: URLSession
|
|
69
|
+
|
|
70
|
+
/// - Parameters:
|
|
71
|
+
/// - appId: `TEAMID.bundleId` as registered in the Authon console; detected from the default
|
|
72
|
+
/// keychain access group when nil.
|
|
73
|
+
/// - anchor: The window for the Apple ID sheet (the key window when nil).
|
|
74
|
+
public convenience init(publishableKey: String, apiURL: String = "https://api.authon.dev", appId: String? = nil,
|
|
75
|
+
anchor: (@MainActor () -> ASPresentationAnchor?)? = nil) {
|
|
76
|
+
self.init(publishableKey: publishableKey, apiURL: apiURL, appId: appId, authorizer: SystemAppleIDAuthorizer(anchor: anchor), session: .shared)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public init(publishableKey: String, apiURL: String, appId: String?, authorizer: AppleIDAuthorizing, session: URLSession = .shared) {
|
|
80
|
+
var b = apiURL
|
|
81
|
+
while b.hasSuffix("/") { b.removeLast() }
|
|
82
|
+
base = b
|
|
83
|
+
self.publishableKey = publishableKey
|
|
84
|
+
self.appId = appId
|
|
85
|
+
self.authorizer = authorizer
|
|
86
|
+
self.session = session
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Runs the sign-in and returns the API's JSON result: a session
|
|
90
|
+
/// (`accessToken`, `refreshToken`, `expiresIn`, `user`) or an MFA challenge (`mfaRequired`, `mfaToken`).
|
|
91
|
+
public func signIn() async throws -> [String: Any] {
|
|
92
|
+
guard let appId = appId ?? Self.detectAppIdentifier() else {
|
|
93
|
+
throw AppleSignInError("OAUTH_REDIRECT_NOT_ALLOWED", "appId (TEAMID.bundleId) could not be determined")
|
|
94
|
+
}
|
|
95
|
+
var verifierBytes = [UInt8](repeating: 0, count: 32)
|
|
96
|
+
guard SecRandomCopyBytes(kSecRandomDefault, verifierBytes.count, &verifierBytes) == errSecSuccess else {
|
|
97
|
+
throw AppleSignInError("OAUTH_START_FAILED", "random source unavailable")
|
|
98
|
+
}
|
|
99
|
+
let verifier = Self.base64url(Data(verifierBytes))
|
|
100
|
+
let challenge = Self.base64url(Data(SHA256.hash(data: Data(verifier.utf8))))
|
|
101
|
+
|
|
102
|
+
let start: [String: Any]
|
|
103
|
+
do {
|
|
104
|
+
start = try await post("/v1/auth/oauth/native/apple/start", ["appId": appId, "codeChallenge": challenge])
|
|
105
|
+
} catch let e as AppleSignInError where e.code == "API_ERROR" {
|
|
106
|
+
throw AppleSignInError("OAUTH_START_FAILED", e.message, diagnosticId: e.diagnosticId, serverCode: e.serverCode, statusCode: e.statusCode)
|
|
107
|
+
}
|
|
108
|
+
guard let attemptId = start["attemptId"] as? String, let nonce = start["nonce"] as? String, !nonce.isEmpty else {
|
|
109
|
+
throw AppleSignInError("OAUTH_START_FAILED", "Unexpected response from the server")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let apple = try await authorizer.authorize(hashedNonce: Self.hashNonce(nonce))
|
|
113
|
+
guard let tokenData = apple.identityToken, let identityToken = String(data: tokenData, encoding: .utf8),
|
|
114
|
+
let codeData = apple.authorizationCode, let authorizationCode = String(data: codeData, encoding: .utf8) else {
|
|
115
|
+
throw AppleSignInError("OAUTH_PROVIDER_ERROR", "Apple returned no identity token or authorization code", retryable: false)
|
|
116
|
+
}
|
|
117
|
+
var body: [String: Any] = ["attemptId": attemptId, "codeVerifier": verifier, "identityToken": identityToken, "authorizationCode": authorizationCode]
|
|
118
|
+
if let name = apple.fullName.map({ PersonNameComponentsFormatter.localizedString(from: $0, style: .default) })?
|
|
119
|
+
.trimmingCharacters(in: .whitespacesAndNewlines), !name.isEmpty {
|
|
120
|
+
body["displayName"] = name
|
|
121
|
+
}
|
|
122
|
+
// Single use: no retry, whatever the outcome.
|
|
123
|
+
return try await post("/v1/auth/oauth/native/apple/complete", body)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Lowercase hex SHA-256 of the server nonce (what ASAuthorizationAppleIDRequest.nonce carries).
|
|
127
|
+
public static func hashNonce(_ nonce: String) -> String {
|
|
128
|
+
SHA256.hash(data: Data(nonce.utf8)).map { String(format: "%02x", $0) }.joined()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static func base64url(_ data: Data) -> String {
|
|
132
|
+
data.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MARK: - HTTP
|
|
136
|
+
|
|
137
|
+
private func post(_ path: String, _ body: [String: Any]) async throws -> [String: Any] {
|
|
138
|
+
guard let url = URL(string: base + path) else { throw AppleSignInError("API_ERROR", "Invalid URL") }
|
|
139
|
+
var req = URLRequest(url: url)
|
|
140
|
+
req.httpMethod = "POST"
|
|
141
|
+
req.setValue("application/json", forHTTPHeaderField: "content-type")
|
|
142
|
+
req.setValue("application/json", forHTTPHeaderField: "accept")
|
|
143
|
+
req.setValue(publishableKey, forHTTPHeaderField: "x-api-key")
|
|
144
|
+
req.httpShouldHandleCookies = false
|
|
145
|
+
req.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
146
|
+
let data: Data
|
|
147
|
+
let response: URLResponse
|
|
148
|
+
do {
|
|
149
|
+
(data, response) = try await session.data(for: req)
|
|
150
|
+
} catch {
|
|
151
|
+
throw AppleSignInError("NETWORK_ERROR", "The server could not be reached")
|
|
152
|
+
}
|
|
153
|
+
let http = response as? HTTPURLResponse
|
|
154
|
+
let status = http?.statusCode ?? 0
|
|
155
|
+
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
|
|
156
|
+
guard (200..<300).contains(status) else {
|
|
157
|
+
let serverCode = json?["code"] as? String
|
|
158
|
+
let id = (json?["requestId"] as? String) ?? http?.value(forHTTPHeaderField: "X-Request-Id")
|
|
159
|
+
let message = (json?["message"] as? String) ?? HTTPURLResponse.localizedString(forStatusCode: status)
|
|
160
|
+
throw AppleSignInError(Self.code(status: status, serverCode: serverCode), message, diagnosticId: id, serverCode: serverCode, statusCode: status)
|
|
161
|
+
}
|
|
162
|
+
guard let json else { throw AppleSignInError("API_ERROR", "Unexpected response from the server", statusCode: status) }
|
|
163
|
+
return json
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static func code(status: Int, serverCode: String?) -> String {
|
|
167
|
+
switch serverCode {
|
|
168
|
+
case "OAUTH_STATE_INVALID": return "OAUTH_STATE_MISMATCH"
|
|
169
|
+
case "OAUTH_EXCHANGE_FAILED": return "OAUTH_EXCHANGE_FAILED"
|
|
170
|
+
case "OAUTH_REDIRECT_NOT_ALLOWED", "APP_NOT_REGISTERED", "APP_NOT_APPROVED": return "OAUTH_REDIRECT_NOT_ALLOWED"
|
|
171
|
+
case "EXPLICIT_IDENTITY_LINK_REQUIRED": return "ACCOUNT_LINK_REQUIRED"
|
|
172
|
+
case "RATE_LIMITED": return "RATE_LIMITED"
|
|
173
|
+
default: return status == 429 ? "RATE_LIMITED" : "API_ERROR"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// `TEAMID.bundleId` from the app's default keychain access group.
|
|
178
|
+
static func detectAppIdentifier() -> String? {
|
|
179
|
+
var q: [String: Any] = [
|
|
180
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
181
|
+
kSecAttrService as String: "dev.authon.apple-sign-in.app-id",
|
|
182
|
+
kSecAttrAccount as String: "probe",
|
|
183
|
+
kSecReturnAttributes as String: true,
|
|
184
|
+
]
|
|
185
|
+
#if os(macOS)
|
|
186
|
+
q[kSecUseDataProtectionKeychain as String] = true
|
|
187
|
+
#endif
|
|
188
|
+
var out: AnyObject?
|
|
189
|
+
var status = SecItemCopyMatching(q as CFDictionary, &out)
|
|
190
|
+
if status == errSecItemNotFound {
|
|
191
|
+
var add = q
|
|
192
|
+
add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
193
|
+
status = SecItemAdd(add as CFDictionary, &out)
|
|
194
|
+
}
|
|
195
|
+
guard status == errSecSuccess, let attrs = out as? [String: Any], let group = attrs[kSecAttrAccessGroup as String] as? String,
|
|
196
|
+
group.contains(".") else { return nil }
|
|
197
|
+
return group
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// The system Apple ID sheet (ASAuthorizationController), shown immediately.
|
|
202
|
+
public final class SystemAppleIDAuthorizer: AppleIDAuthorizing, @unchecked Sendable {
|
|
203
|
+
private let anchor: (@MainActor () -> ASPresentationAnchor?)?
|
|
204
|
+
|
|
205
|
+
public init(anchor: (@MainActor () -> ASPresentationAnchor?)? = nil) {
|
|
206
|
+
self.anchor = anchor
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@MainActor
|
|
210
|
+
public func authorize(hashedNonce: String) async throws -> AppleIDResult {
|
|
211
|
+
let request = ASAuthorizationAppleIDProvider().createRequest()
|
|
212
|
+
request.requestedScopes = [.fullName, .email]
|
|
213
|
+
request.nonce = hashedNonce
|
|
214
|
+
let delegate = AppleIDDelegate(anchor: anchor?() ?? Self.keyWindow())
|
|
215
|
+
let credential = try await delegate.run(request)
|
|
216
|
+
return AppleIDResult(identityToken: credential.identityToken, authorizationCode: credential.authorizationCode, fullName: credential.fullName)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@MainActor
|
|
220
|
+
static func keyWindow() -> ASPresentationAnchor {
|
|
221
|
+
#if canImport(UIKit)
|
|
222
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
223
|
+
return scenes.flatMap(\.windows).first(where: \.isKeyWindow) ?? scenes.first?.windows.first ?? ASPresentationAnchor()
|
|
224
|
+
#else
|
|
225
|
+
return NSApplication.shared.keyWindow ?? NSApplication.shared.windows.first ?? ASPresentationAnchor()
|
|
226
|
+
#endif
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// ASAuthorizationError → sign-in code. Only `.canceled` is a cancel; everything else (including
|
|
230
|
+
/// 1000 `.unknown` after dismissing "sign in to your Apple account") is a provider error that
|
|
231
|
+
/// retrying the same sheet will not fix.
|
|
232
|
+
static func map(_ error: Error) -> AppleSignInError {
|
|
233
|
+
if let e = error as? ASAuthorizationError, e.code == .canceled {
|
|
234
|
+
return AppleSignInError("OAUTH_CANCELLED", "Sign in with Apple was cancelled")
|
|
235
|
+
}
|
|
236
|
+
return AppleSignInError("OAUTH_PROVIDER_ERROR", "Sign in with Apple failed (\((error as NSError).code))", retryable: false)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@MainActor
|
|
241
|
+
private final class AppleIDDelegate: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
|
|
242
|
+
private let anchor: ASPresentationAnchor
|
|
243
|
+
private var continuation: CheckedContinuation<ASAuthorizationAppleIDCredential, Error>?
|
|
244
|
+
private var retain: AppleIDDelegate?
|
|
245
|
+
|
|
246
|
+
init(anchor: ASPresentationAnchor) {
|
|
247
|
+
self.anchor = anchor
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
func run(_ request: ASAuthorizationRequest) async throws -> ASAuthorizationAppleIDCredential {
|
|
251
|
+
try await withCheckedThrowingContinuation { cont in
|
|
252
|
+
continuation = cont
|
|
253
|
+
retain = self
|
|
254
|
+
let controller = ASAuthorizationController(authorizationRequests: [request])
|
|
255
|
+
controller.delegate = self
|
|
256
|
+
controller.presentationContextProvider = self
|
|
257
|
+
controller.performRequests()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
nonisolated func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
262
|
+
MainActor.assumeIsolated { anchor }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
nonisolated func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
|
266
|
+
MainActor.assumeIsolated {
|
|
267
|
+
if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
|
|
268
|
+
finish(.success(credential))
|
|
269
|
+
} else {
|
|
270
|
+
finish(.failure(AppleSignInError("OAUTH_PROVIDER_ERROR", "Unexpected Apple credential type", retryable: false)))
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
nonisolated func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
|
276
|
+
let mapped = SystemAppleIDAuthorizer.map(error)
|
|
277
|
+
MainActor.assumeIsolated { finish(.failure(mapped)) }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private func finish(_ result: Result<ASAuthorizationAppleIDCredential, Error>) {
|
|
281
|
+
continuation?.resume(with: result)
|
|
282
|
+
continuation = nil
|
|
283
|
+
retain = nil
|
|
284
|
+
}
|
|
285
|
+
}
|
package/ios/vendor/GENERATED.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# Generated sources
|
|
2
2
|
|
|
3
|
-
Everything in this folder is copied from `swift/Sources/{AuthonWallet,CSecp256k1,CKeccak,CMonocypher}` by
|
|
3
|
+
Everything in this folder is copied from `swift/Sources/{AuthonWallet,AuthonAppleSignIn,CSecp256k1,CKeccak,CMonocypher}` by
|
|
4
4
|
`scripts/sync-native-wallet.mjs` (run by the package build). Do not edit these files; change the native SDK sources and run the script. `--check` fails on any difference.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authon/react-native-wallet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Authon embedded wallet for React Native (Expo native module wrapping the Swift and Kotlin wallet SDKs)",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { getNativeModule } from './native';
|
|
3
|
+
|
|
4
|
+
declare const require: (id: string) => unknown;
|
|
5
|
+
|
|
6
|
+
/** The part of @authon/react-native's AuthonMobileClient this helper uses. */
|
|
7
|
+
export interface AppleSignInClient {
|
|
8
|
+
getApiUrl(): string;
|
|
9
|
+
getPublishableKey(): string;
|
|
10
|
+
adoptSignInResult(result: never): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type AuthErrorCtor = new (
|
|
14
|
+
code: string,
|
|
15
|
+
message?: string,
|
|
16
|
+
options?: { diagnosticId?: string | null; retryable?: boolean },
|
|
17
|
+
) => Error & { code: string; diagnosticId: string | null; retryable: boolean };
|
|
18
|
+
|
|
19
|
+
/** Fallback with the same shape as @authon/react-native's AuthonAuthError. */
|
|
20
|
+
class AppleSignInError extends Error {
|
|
21
|
+
readonly code: string;
|
|
22
|
+
readonly diagnosticId: string | null;
|
|
23
|
+
readonly retryable: boolean;
|
|
24
|
+
constructor(code: string, message?: string, options: { diagnosticId?: string | null; retryable?: boolean } = {}) {
|
|
25
|
+
super(message ?? code);
|
|
26
|
+
this.name = 'AuthonAuthError';
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.diagnosticId = options.diagnosticId ?? null;
|
|
29
|
+
this.retryable = options.retryable ?? false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function authError(code: string, message: string, options: { diagnosticId?: string | null; retryable?: boolean } = {}) {
|
|
34
|
+
let Ctor: AuthErrorCtor = AppleSignInError;
|
|
35
|
+
try {
|
|
36
|
+
const mod = require('@authon/react-native') as { AuthonAuthError?: AuthErrorCtor } | undefined;
|
|
37
|
+
if (mod?.AuthonAuthError) Ctor = mod.AuthonAuthError;
|
|
38
|
+
} catch {
|
|
39
|
+
// @authon/react-native not installed: same fields, local class.
|
|
40
|
+
}
|
|
41
|
+
return new Ctor(code, message, options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SignInWithAppleNativeOptions {
|
|
45
|
+
/** `TEAMID.bundleId` as registered in the Authon console; detected from the app when omitted. */
|
|
46
|
+
appId?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Native Sign in with Apple on iOS: shows the system Apple sheet right away (no browser, no web page)
|
|
51
|
+
* and makes the resulting session the session of `client` (an AuthonMobileClient from
|
|
52
|
+
* @authon/react-native, e.g. `useAuthon().client`), like a completed OAuth sign-in.
|
|
53
|
+
*
|
|
54
|
+
* JS never sees Apple's identity token, authorization code, nonce or the PKCE verifier; only the
|
|
55
|
+
* final result reaches JS. Errors are AuthonAuthError with the shared sign-in codes: OAUTH_CANCELLED
|
|
56
|
+
* (the user closed the sheet, show nothing), OAUTH_REDIRECT_NOT_ALLOWED (app not registered or not
|
|
57
|
+
* approved; `serverCode` tells APP_NOT_REGISTERED from APP_NOT_APPROVED), OAUTH_STATE_MISMATCH
|
|
58
|
+
* (server OAUTH_STATE_INVALID), OAUTH_EXCHANGE_FAILED, ACCOUNT_LINK_REQUIRED, NETWORK_ERROR,
|
|
59
|
+
* RATE_LIMITED, API_ERROR, with `diagnosticId` when the server answered.
|
|
60
|
+
* MFA: throws AuthonMfaRequiredError, finish with verifyMfa(). Cancel and errors never fall back to
|
|
61
|
+
* a web flow. Android: UNSUPPORTED_PLATFORM, use the normal OAuth flow (`startOAuth('apple')`).
|
|
62
|
+
*/
|
|
63
|
+
export async function signInWithAppleNative(client: AppleSignInClient, options: SignInWithAppleNativeOptions = {}): Promise<unknown> {
|
|
64
|
+
if (Platform.OS !== 'ios') {
|
|
65
|
+
throw authError('UNSUPPORTED_PLATFORM', 'Native Sign in with Apple is iOS only; on Android use startOAuth("apple")');
|
|
66
|
+
}
|
|
67
|
+
const native = getNativeModule();
|
|
68
|
+
if (!native?.signInWithApple) {
|
|
69
|
+
throw authError('NOT_CONFIGURED', '@authon/react-native-wallet native module not found. Use a development build and rebuild the app.');
|
|
70
|
+
}
|
|
71
|
+
const result = await native.signInWithApple({
|
|
72
|
+
publishableKey: client.getPublishableKey(),
|
|
73
|
+
apiUrl: client.getApiUrl(),
|
|
74
|
+
appId: options.appId ?? null,
|
|
75
|
+
});
|
|
76
|
+
if (!result || typeof result !== 'object') throw authError('API_ERROR', 'Apple sign-in returned no result');
|
|
77
|
+
if (result.ok !== true) {
|
|
78
|
+
const code = typeof result.code === 'string' && result.code ? result.code : 'API_ERROR';
|
|
79
|
+
const error = authError(code, typeof result.message === 'string' && result.message ? result.message : code, {
|
|
80
|
+
diagnosticId: typeof result.diagnosticId === 'string' && result.diagnosticId ? result.diagnosticId : null,
|
|
81
|
+
retryable: result.retryable === true,
|
|
82
|
+
});
|
|
83
|
+
// The server's own code (APP_NOT_REGISTERED, APP_NOT_APPROVED, OAUTH_STATE_INVALID, ...), when sent.
|
|
84
|
+
const serverCode = (result as { serverCode?: unknown }).serverCode;
|
|
85
|
+
throw Object.assign(error, { serverCode: typeof serverCode === 'string' && serverCode ? serverCode : null });
|
|
86
|
+
}
|
|
87
|
+
let session: unknown;
|
|
88
|
+
try {
|
|
89
|
+
session = typeof result.value === 'string' ? JSON.parse(result.value) : result.value;
|
|
90
|
+
} catch {
|
|
91
|
+
throw authError('API_ERROR', 'Apple sign-in returned an unreadable result');
|
|
92
|
+
}
|
|
93
|
+
return client.adoptSignInResult(session as never);
|
|
94
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { AuthonWalletClient, createAuthonWallet } from './client';
|
|
|
2
2
|
export { AuthonWalletError } from './errors';
|
|
3
3
|
export type { WalletErrorCode } from './errors';
|
|
4
4
|
export { useAuthonWallet } from './useAuthonWallet';
|
|
5
|
+
export { signInWithAppleNative } from './appleSignIn';
|
|
6
|
+
export type { AppleSignInClient, SignInWithAppleNativeOptions } from './appleSignIn';
|
|
5
7
|
export type { UseAuthonWalletOptions, UseAuthonWalletResult } from './useAuthonWallet';
|
|
6
8
|
export type {
|
|
7
9
|
AuthonWalletOptions,
|
package/src/native.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface AuthonWalletNativeModule {
|
|
|
37
37
|
upgradeBackup(id: string, token: string): Promise<NativeResult<unknown>>;
|
|
38
38
|
restore(id: string, token: string, reset: boolean): Promise<NativeResult<unknown>>;
|
|
39
39
|
revokeDevice(id: string, token: string, deviceId: string): Promise<NativeResult<unknown>>;
|
|
40
|
+
/** iOS only: native Sign in with Apple; resolves the server's sign-in result (JSON), never Apple's tokens. */
|
|
41
|
+
signInWithApple?(config: { publishableKey: string; apiUrl: string; appId: string | null }): Promise<NativeResult<unknown>>;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
let override: AuthonWalletNativeModule | null | undefined;
|