@atlasauth/expo-passkeys 0.1.0 → 0.2.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 +22 -0
- package/README.md +16 -0
- package/dist/client.js +7 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @atlasauth/expo-passkeys changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-10-23
|
|
4
|
+
- **Passkey sign-in takes the sign-in captcha.** `authenticateWithPasskey()` accepts `captchaToken` (sent on the start) and `getCaptchaToken` (asked for a fresh token after a `CAPTCHA_REQUIRED` / `CAPTCHA_FAILED` refusal; the start is retried once). Needed when the app challenges every sign-in.
|
|
5
|
+
- Built on `@atlasauth/js` 0.16.0.
|
|
6
|
+
|
|
7
|
+
## 0.1.1
|
|
8
|
+
- **`frontendApi` is validated at client creation.** A missing, empty or malformed value now throws an
|
|
9
|
+
`AtlasConfigError` (from `@atlasauth/js`; `code: 'FRONTEND_API_REQUIRED'` or `'FRONTEND_API_INVALID'`)
|
|
10
|
+
instead of silently sending requests to relative URLs on the app's own origin. Accepted: an absolute
|
|
11
|
+
`https:` URL (`http:` only for localhost / local development hosts). Native apps have no origin of their own, so `"same-origin"` is refused.
|
|
12
|
+
|
|
13
|
+
## 0.1.0
|
|
14
|
+
|
|
15
|
+
First published release.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `registerPasskey()` runs the passkey registration ceremony against Atlas on Expo / React Native.
|
|
19
|
+
- `authenticateWithPasskey()` signs in with a passkey.
|
|
20
|
+
- `toRegistrationFinishBody()` and `toAssertionFinishBody()` map native authenticator results to the Atlas request bodies.
|
|
21
|
+
- `createFapiClient()` / `AtlasFapiConfig` for configuring the Atlas instance.
|
|
22
|
+
- Works with `react-native-passkey`, passed in as an optional peer and never imported directly.
|
package/README.md
CHANGED
|
@@ -64,5 +64,21 @@ if (result.status === 'complete' && result.ticket) {
|
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### When the app challenges every sign-in
|
|
68
|
+
|
|
69
|
+
If the app asks for a security check on every sign-in, the sign-in start needs a captcha token.
|
|
70
|
+
Pass a solved `captchaToken`, or a `getCaptchaToken` callback, or both. The callback is called after
|
|
71
|
+
the server refuses the start (`CAPTCHA_REQUIRED` or `CAPTCHA_FAILED`). It receives the app's public
|
|
72
|
+
sign-in captcha settings (provider, site key). Run that provider's challenge, for example in a
|
|
73
|
+
WebView, and return the solved token. The start is then retried once with it.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const result = await authenticateWithPasskey({
|
|
77
|
+
config,
|
|
78
|
+
passkey: Passkey,
|
|
79
|
+
getCaptchaToken: async ({ captcha }) => solveInWebView(captcha), // your own challenge screen
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
67
83
|
`toRegistrationFinishBody` / `toAssertionFinishBody` are exported as pure helpers
|
|
68
84
|
if you want to drive the two HTTP calls yourself.
|
package/dist/client.js
CHANGED
|
@@ -4,6 +4,12 @@ exports.createFapiClient = createFapiClient;
|
|
|
4
4
|
const js_1 = require("@atlasauth/js");
|
|
5
5
|
/** Build a bearer-authenticated `@atlasauth/js` FapiClient from the config. */
|
|
6
6
|
function createFapiClient(config) {
|
|
7
|
+
// A native app has no origin of its own: a missing value, "same-origin" or a
|
|
8
|
+
// bare path throws an `AtlasConfigError` instead of producing relative URLs.
|
|
9
|
+
const baseUrl = (0, js_1.resolveFrontendApi)(config.frontendApi, {
|
|
10
|
+
component: 'createFapiClient()',
|
|
11
|
+
allowSameOrigin: false,
|
|
12
|
+
});
|
|
7
13
|
const base = config.fetchImpl ?? fetch;
|
|
8
14
|
const fetchImpl = async (input, init) => {
|
|
9
15
|
const headers = new Headers(init?.headers);
|
|
@@ -14,7 +20,7 @@ function createFapiClient(config) {
|
|
|
14
20
|
};
|
|
15
21
|
return new js_1.FapiClient({
|
|
16
22
|
publishableKey: config.publishableKey,
|
|
17
|
-
baseUrl
|
|
23
|
+
baseUrl,
|
|
18
24
|
fetchImpl,
|
|
19
25
|
});
|
|
20
26
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;AAqBA,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;AAqBA,4CAmBC;AAxCD,sCAA+D;AAoB/D,+EAA+E;AAC/E,SAAgB,gBAAgB,CAAC,MAAuB;IACtD,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAA,uBAAkB,EAAC,MAAM,CAAC,WAAW,EAAE;QACrD,SAAS,EAAE,oBAAoB;QAC/B,eAAe,EAAE,KAAK;KACvB,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,IAAI,KAAK,CAAC;IACvC,MAAM,SAAS,GAAiB,KAAK,EAAE,KAAwB,EAAE,IAAkB,EAAE,EAAE;QACrF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,EAAE,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;IACF,OAAO,IAAI,eAAU,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,OAAO;QACP,SAAS;KACV,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* register: POST /v1/client/me/passkeys/begin → Passkey.create → /finish
|
|
13
13
|
* sign in : POST /v1/client/sign_ins/passkey/begin → Passkey.get → /finish
|
|
14
14
|
*/
|
|
15
|
-
import type
|
|
15
|
+
import { type FieldError, type FlowCaptcha, type PasskeyAssertionBody, type PasskeyRegistrationBody } from '@atlasauth/js';
|
|
16
16
|
import { type AtlasFapiConfig } from './client';
|
|
17
17
|
import type { PasskeyCreateResult, PasskeyGetResult, ReactNativePasskey } from './passkey-native';
|
|
18
18
|
export * from './client';
|
|
@@ -43,13 +43,37 @@ export interface PasskeySignInResult {
|
|
|
43
43
|
attemptStatus?: string;
|
|
44
44
|
errors: FieldError[];
|
|
45
45
|
}
|
|
46
|
+
/** What {@link authenticateWithPasskey}'s `getCaptchaToken` is asked for. */
|
|
47
|
+
export interface PasskeyCaptchaTokenRequest {
|
|
48
|
+
/** Always `signIn`: the passkey begin takes the sign-in challenge. */
|
|
49
|
+
flow: 'signIn';
|
|
50
|
+
/**
|
|
51
|
+
* The instance's public sign-in captcha settings (provider, site key, widget
|
|
52
|
+
* mode) from `GET /v1/appearance`; null if they could not be read.
|
|
53
|
+
*/
|
|
54
|
+
captcha: FlowCaptcha | null;
|
|
55
|
+
/** The refusal that prompted the ask. */
|
|
56
|
+
refused: 'CAPTCHA_REQUIRED' | 'CAPTCHA_FAILED';
|
|
57
|
+
}
|
|
46
58
|
/**
|
|
47
59
|
* Sign in with a passkey. Runs begin → platform ceremony → finish and returns
|
|
48
60
|
* the attempt outcome: `complete` carries a one-time `ticket` to exchange for a
|
|
49
61
|
* session (hand it to `@atlasauth/expo`), `needs_more` means a factor is still
|
|
50
62
|
* owed.
|
|
63
|
+
*
|
|
64
|
+
* When the instance challenges every sign-in (an always-on sign-in captcha, or
|
|
65
|
+
* a force-challenge switch) the begin needs a `captcha_token`. Pass a solved
|
|
66
|
+
* `captchaToken` up front, and/or a `getCaptchaToken` callback: after a
|
|
67
|
+
* `CAPTCHA_REQUIRED` / `CAPTCHA_FAILED` refusal it is asked for a FRESH token
|
|
68
|
+
* (run the provider's challenge, e.g. in a WebView, with `captcha.siteKey`) and
|
|
69
|
+
* the begin is retried once. Without either, the refusal comes back as the
|
|
70
|
+
* result's error.
|
|
51
71
|
*/
|
|
52
72
|
export declare function authenticateWithPasskey(opts: {
|
|
53
73
|
config: AtlasFapiConfig;
|
|
54
74
|
passkey: ReactNativePasskey;
|
|
75
|
+
/** A solved sign-in captcha token, sent on the first begin. */
|
|
76
|
+
captchaToken?: string;
|
|
77
|
+
/** Asked for a fresh token after a captcha refusal; the begin is then retried once. */
|
|
78
|
+
getCaptchaToken?: (request: PasskeyCaptchaTokenRequest) => Promise<string | null>;
|
|
55
79
|
}): Promise<PasskeySignInResult>;
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.toRegistrationFinishBody = toRegistrationFinishBody;
|
|
|
32
32
|
exports.toAssertionFinishBody = toAssertionFinishBody;
|
|
33
33
|
exports.registerPasskey = registerPasskey;
|
|
34
34
|
exports.authenticateWithPasskey = authenticateWithPasskey;
|
|
35
|
+
const js_1 = require("@atlasauth/js");
|
|
35
36
|
const client_1 = require("./client");
|
|
36
37
|
__exportStar(require("./client"), exports);
|
|
37
38
|
__exportStar(require("./passkey-native"), exports);
|
|
@@ -76,10 +77,34 @@ async function registerPasskey(opts) {
|
|
|
76
77
|
* the attempt outcome: `complete` carries a one-time `ticket` to exchange for a
|
|
77
78
|
* session (hand it to `@atlasauth/expo`), `needs_more` means a factor is still
|
|
78
79
|
* owed.
|
|
80
|
+
*
|
|
81
|
+
* When the instance challenges every sign-in (an always-on sign-in captcha, or
|
|
82
|
+
* a force-challenge switch) the begin needs a `captcha_token`. Pass a solved
|
|
83
|
+
* `captchaToken` up front, and/or a `getCaptchaToken` callback: after a
|
|
84
|
+
* `CAPTCHA_REQUIRED` / `CAPTCHA_FAILED` refusal it is asked for a FRESH token
|
|
85
|
+
* (run the provider's challenge, e.g. in a WebView, with `captcha.siteKey`) and
|
|
86
|
+
* the begin is retried once. Without either, the refusal comes back as the
|
|
87
|
+
* result's error.
|
|
79
88
|
*/
|
|
80
89
|
async function authenticateWithPasskey(opts) {
|
|
81
90
|
const client = (0, client_1.createFapiClient)(opts.config);
|
|
82
|
-
const
|
|
91
|
+
const { captchaToken, getCaptchaToken } = opts;
|
|
92
|
+
const begin = await (0, js_1.beginPasskeySignIn)(client, {
|
|
93
|
+
captcha: captchaToken || getCaptchaToken
|
|
94
|
+
? async ({ retry, refused }) => {
|
|
95
|
+
if (!retry)
|
|
96
|
+
return captchaToken ?? null;
|
|
97
|
+
if (!getCaptchaToken)
|
|
98
|
+
return null;
|
|
99
|
+
const settings = await (0, js_1.fetchInstanceSettings)(client);
|
|
100
|
+
return getCaptchaToken({
|
|
101
|
+
flow: 'signIn',
|
|
102
|
+
captcha: settings?.captchaFlows.signIn ?? null,
|
|
103
|
+
refused: refused ?? 'CAPTCHA_REQUIRED',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
: null,
|
|
107
|
+
});
|
|
83
108
|
if (!begin.ok || !begin.data)
|
|
84
109
|
return { status: 'error', errors: begin.errors };
|
|
85
110
|
const assertion = await opts.passkey.get(begin.data);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlasauth/expo-passkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@atlasauth/js": "0.
|
|
7
|
+
"@atlasauth/js": "0.16.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
10
|
"react-native": ">=0.73",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"README.md"
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc -p tsconfig.build.json",
|