react-native-nitro-passkeys 0.1.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/LICENSE +21 -0
- package/NitroPasskeys.podspec +37 -0
- package/README.md +314 -0
- package/android/CMakeLists.txt +31 -0
- package/android/build.gradle +155 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/margelo/nitro/nitropasskeys/HybridNitroPasskeys.kt +65 -0
- package/android/src/main/java/com/margelo/nitro/nitropasskeys/NitroPasskeysPackage.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/nitropasskeys/PasskeyErrorToken.kt +64 -0
- package/android/src/test/java/com/margelo/nitro/nitropasskeys/PasskeyErrorTokenTest.kt +128 -0
- package/ios/Base64URL.swift +37 -0
- package/ios/Bridge.h +1 -0
- package/ios/HybridNitroPasskeys.swift +290 -0
- package/ios/PasskeyErrorToken.swift +32 -0
- package/ios/PasskeyJSON.swift +195 -0
- package/lib/commonjs/PasskeyError.js +39 -0
- package/lib/commonjs/PasskeyError.js.map +1 -0
- package/lib/commonjs/index.js +52 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/response.js +30 -0
- package/lib/commonjs/response.js.map +1 -0
- package/lib/commonjs/specs/nitro-passkeys.nitro.js +6 -0
- package/lib/commonjs/specs/nitro-passkeys.nitro.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/validate.js +118 -0
- package/lib/commonjs/validate.js.map +1 -0
- package/lib/module/PasskeyError.js +34 -0
- package/lib/module/PasskeyError.js.map +1 -0
- package/lib/module/index.js +42 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/response.js +25 -0
- package/lib/module/response.js.map +1 -0
- package/lib/module/specs/nitro-passkeys.nitro.js +4 -0
- package/lib/module/specs/nitro-passkeys.nitro.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/validate.js +109 -0
- package/lib/module/validate.js.map +1 -0
- package/lib/typescript/example/server/index.d.ts +2 -0
- package/lib/typescript/example/server/index.d.ts.map +1 -0
- package/lib/typescript/src/PasskeyError.d.ts +8 -0
- package/lib/typescript/src/PasskeyError.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/response.d.ts +4 -0
- package/lib/typescript/src/response.d.ts.map +1 -0
- package/lib/typescript/src/specs/nitro-passkeys.nitro.d.ts +12 -0
- package/lib/typescript/src/specs/nitro-passkeys.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +104 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/validate.d.ts +14 -0
- package/lib/typescript/src/validate.d.ts.map +1 -0
- package/nitro.json +24 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroPasskeys+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroPasskeys+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroPasskeysOnLoad.cpp +54 -0
- package/nitrogen/generated/android/NitroPasskeysOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JHybridNitroPasskeysSpec.cpp +86 -0
- package/nitrogen/generated/android/c++/JHybridNitroPasskeysSpec.hpp +64 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitropasskeys/HybridNitroPasskeysSpec.kt +63 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitropasskeys/NitroPasskeysOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroPasskeys+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroPasskeys-Swift-Cxx-Bridge.cpp +49 -0
- package/nitrogen/generated/ios/NitroPasskeys-Swift-Cxx-Bridge.hpp +111 -0
- package/nitrogen/generated/ios/NitroPasskeys-Swift-Cxx-Umbrella.hpp +45 -0
- package/nitrogen/generated/ios/NitroPasskeysAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroPasskeysAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridNitroPasskeysSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroPasskeysSpecSwift.hpp +93 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNitroPasskeysSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridNitroPasskeysSpec_cxx.swift +169 -0
- package/nitrogen/generated/shared/c++/HybridNitroPasskeysSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridNitroPasskeysSpec.hpp +64 -0
- package/package.json +106 -0
- package/src/PasskeyError.ts +58 -0
- package/src/index.ts +66 -0
- package/src/response.ts +49 -0
- package/src/specs/nitro-passkeys.nitro.ts +12 -0
- package/src/types.ts +124 -0
- package/src/validate.ts +132 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
|
|
4
|
+
/// The WebAuthn JSON shapes iOS reads and writes. Decoders only declare the fields iOS uses;
|
|
5
|
+
/// unknown keys (`timeout`, `hints`, `extensions`, …) are ignored.
|
|
6
|
+
enum PasskeyJSON {
|
|
7
|
+
// MARK: Options (JS → native)
|
|
8
|
+
|
|
9
|
+
struct CreationOptions: Decodable {
|
|
10
|
+
// swiftlint:disable:next identifier_name
|
|
11
|
+
let rp: RelyingParty
|
|
12
|
+
let user: User
|
|
13
|
+
let challenge: String
|
|
14
|
+
let excludeCredentials: [Descriptor]?
|
|
15
|
+
let authenticatorSelection: Selection?
|
|
16
|
+
let attestation: String?
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct RelyingParty: Decodable {
|
|
20
|
+
let id: String?
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct User: Decodable {
|
|
24
|
+
let id: String
|
|
25
|
+
let name: String
|
|
26
|
+
let displayName: String?
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
struct Descriptor: Decodable {
|
|
30
|
+
let id: String
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
struct Selection: Decodable {
|
|
34
|
+
let userVerification: String?
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
struct RequestOptions: Decodable {
|
|
38
|
+
let challenge: String
|
|
39
|
+
let rpId: String?
|
|
40
|
+
let allowCredentials: [Descriptor]?
|
|
41
|
+
let userVerification: String?
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: Responses (native → JS)
|
|
45
|
+
|
|
46
|
+
struct RegistrationResponseBody: Encodable {
|
|
47
|
+
let clientDataJSON: String
|
|
48
|
+
let attestationObject: String
|
|
49
|
+
let transports: [String]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
struct RegistrationResponse: Encodable {
|
|
53
|
+
let id: String
|
|
54
|
+
let rawId: String
|
|
55
|
+
let type: String
|
|
56
|
+
let authenticatorAttachment: String
|
|
57
|
+
let clientExtensionResults: [String: String]
|
|
58
|
+
let response: RegistrationResponseBody
|
|
59
|
+
|
|
60
|
+
init(credentialID: Data, clientDataJSON: Data, attestationObject: Data, attachment: String) {
|
|
61
|
+
let encodedID = Base64URL.encode(credentialID)
|
|
62
|
+
id = encodedID
|
|
63
|
+
rawId = encodedID
|
|
64
|
+
type = "public-key"
|
|
65
|
+
authenticatorAttachment = attachment
|
|
66
|
+
clientExtensionResults = [:]
|
|
67
|
+
response = RegistrationResponseBody(
|
|
68
|
+
clientDataJSON: Base64URL.encode(clientDataJSON),
|
|
69
|
+
attestationObject: Base64URL.encode(attestationObject),
|
|
70
|
+
transports: ["internal", "hybrid"]
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
struct AssertionResponseBody: Encodable {
|
|
76
|
+
let clientDataJSON: String
|
|
77
|
+
let authenticatorData: String
|
|
78
|
+
let signature: String
|
|
79
|
+
/// `nil` is omitted from the output by the synthesized `encodeIfPresent`.
|
|
80
|
+
let userHandle: String?
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
struct AssertionResponse: Encodable {
|
|
84
|
+
let id: String
|
|
85
|
+
let rawId: String
|
|
86
|
+
let type: String
|
|
87
|
+
let authenticatorAttachment: String
|
|
88
|
+
let clientExtensionResults: [String: String]
|
|
89
|
+
let response: AssertionResponseBody
|
|
90
|
+
|
|
91
|
+
init(
|
|
92
|
+
credentialID: Data,
|
|
93
|
+
clientDataJSON: Data,
|
|
94
|
+
authenticatorData: Data,
|
|
95
|
+
signature: Data,
|
|
96
|
+
userID: Data?,
|
|
97
|
+
attachment: String
|
|
98
|
+
) {
|
|
99
|
+
let encodedID = Base64URL.encode(credentialID)
|
|
100
|
+
id = encodedID
|
|
101
|
+
rawId = encodedID
|
|
102
|
+
type = "public-key"
|
|
103
|
+
authenticatorAttachment = attachment
|
|
104
|
+
clientExtensionResults = [:]
|
|
105
|
+
let userHandle = userID.flatMap { $0.isEmpty ? nil : Base64URL.encode($0) }
|
|
106
|
+
response = AssertionResponseBody(
|
|
107
|
+
clientDataJSON: Base64URL.encode(clientDataJSON),
|
|
108
|
+
authenticatorData: Base64URL.encode(authenticatorData),
|
|
109
|
+
signature: Base64URL.encode(signature),
|
|
110
|
+
userHandle: userHandle
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static func encode<T: Encodable>(_ value: T) throws -> String {
|
|
116
|
+
let encoder = JSONEncoder()
|
|
117
|
+
encoder.outputFormatting = [.withoutEscapingSlashes]
|
|
118
|
+
guard let json = String(bytes: try encoder.encode(value), encoding: .utf8) else {
|
|
119
|
+
let message = PasskeyErrorToken.message(token: "unknown", "Could not encode the response")
|
|
120
|
+
throw RuntimeError.error(withMessage: message)
|
|
121
|
+
}
|
|
122
|
+
return json
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: - Validated parameters
|
|
127
|
+
|
|
128
|
+
/// Decoded, validated `create` inputs. Plain values only, so they can cross to the main thread.
|
|
129
|
+
struct RegistrationParameters: Sendable {
|
|
130
|
+
let rpID: String
|
|
131
|
+
let challenge: Data
|
|
132
|
+
let userName: String
|
|
133
|
+
let userID: Data
|
|
134
|
+
let displayName: String?
|
|
135
|
+
let userVerification: String?
|
|
136
|
+
let attestation: String
|
|
137
|
+
let excludedCredentialIDs: [Data]
|
|
138
|
+
|
|
139
|
+
init(json: String) throws {
|
|
140
|
+
let options = try decodeOptions(PasskeyJSON.CreationOptions.self, from: json, label: "creation")
|
|
141
|
+
guard let rpID = options.rp.id, !rpID.isEmpty else {
|
|
142
|
+
throw invalidOptions("rp.id is required")
|
|
143
|
+
}
|
|
144
|
+
self.rpID = rpID
|
|
145
|
+
challenge = try decodeField(options.challenge, "challenge")
|
|
146
|
+
userName = options.user.name
|
|
147
|
+
userID = try decodeField(options.user.id, "user.id")
|
|
148
|
+
displayName = options.user.displayName
|
|
149
|
+
userVerification = options.authenticatorSelection?.userVerification
|
|
150
|
+
attestation = options.attestation ?? "none"
|
|
151
|
+
excludedCredentialIDs = try (options.excludeCredentials ?? []).enumerated().map { index, descriptor in
|
|
152
|
+
try decodeField(descriptor.id, "excludeCredentials[\(index)].id")
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Decoded, validated `get` inputs.
|
|
158
|
+
struct AssertionParameters: Sendable {
|
|
159
|
+
let rpID: String
|
|
160
|
+
let challenge: Data
|
|
161
|
+
let userVerification: String?
|
|
162
|
+
let allowedCredentialIDs: [Data]
|
|
163
|
+
|
|
164
|
+
init(json: String) throws {
|
|
165
|
+
let options = try decodeOptions(PasskeyJSON.RequestOptions.self, from: json, label: "request")
|
|
166
|
+
guard let rpID = options.rpId, !rpID.isEmpty else {
|
|
167
|
+
throw invalidOptions("rpId is required")
|
|
168
|
+
}
|
|
169
|
+
self.rpID = rpID
|
|
170
|
+
challenge = try decodeField(options.challenge, "challenge")
|
|
171
|
+
userVerification = options.userVerification
|
|
172
|
+
allowedCredentialIDs = try (options.allowCredentials ?? []).enumerated().map { index, descriptor in
|
|
173
|
+
try decodeField(descriptor.id, "allowCredentials[\(index)].id")
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func invalidOptions(_ text: String) -> Error {
|
|
179
|
+
RuntimeError.error(withMessage: PasskeyErrorToken.message(token: "invalidOptions", text))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private func decodeOptions<T: Decodable>(_ type: T.Type, from json: String, label: String) throws -> T {
|
|
183
|
+
do {
|
|
184
|
+
return try JSONDecoder().decode(type, from: Data(json.utf8))
|
|
185
|
+
} catch {
|
|
186
|
+
throw invalidOptions("Invalid \(label) options JSON: \(error.localizedDescription)")
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func decodeField(_ value: String, _ name: String) throws -> Data {
|
|
191
|
+
guard let data = Base64URL.decode(value) else {
|
|
192
|
+
throw invalidOptions("\(name) is not valid base64url")
|
|
193
|
+
}
|
|
194
|
+
return data
|
|
195
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PasskeyError = void 0;
|
|
7
|
+
const CODES = new Set(['canceled', 'noCredentials', 'alreadyRegistered', 'domainNotAssociated', 'unsupported', 'invalidOptions', 'unknown']);
|
|
8
|
+
const TOKEN_RE = /\[Passkeys:([A-Za-z0-9-]+)\]\s?/;
|
|
9
|
+
const ASAUTH_RE = /^asauth-\d+$/;
|
|
10
|
+
function codeForToken(token) {
|
|
11
|
+
if (token === undefined) return 'unknown';
|
|
12
|
+
if (token === 'asauth-1001') return 'canceled';
|
|
13
|
+
if (token === 'asauth-1006') return 'alreadyRegistered';
|
|
14
|
+
if (ASAUTH_RE.test(token)) return 'unknown';
|
|
15
|
+
return CODES.has(token) ? token : 'unknown';
|
|
16
|
+
}
|
|
17
|
+
class PasskeyError extends Error {
|
|
18
|
+
constructor(code, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'PasskeyError';
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Normalises any thrown value into a `PasskeyError`. Never throws. */
|
|
25
|
+
static from(error) {
|
|
26
|
+
try {
|
|
27
|
+
if (error instanceof PasskeyError) return error;
|
|
28
|
+
const original = error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unknown error';
|
|
29
|
+
const match = TOKEN_RE.exec(original);
|
|
30
|
+
const code = codeForToken(match?.[1]);
|
|
31
|
+
const stripped = match ? original.replace(TOKEN_RE, '') : original;
|
|
32
|
+
return new PasskeyError(code, stripped.length > 0 ? stripped : original);
|
|
33
|
+
} catch {
|
|
34
|
+
return new PasskeyError('unknown', 'Unknown error');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.PasskeyError = PasskeyError;
|
|
39
|
+
//# sourceMappingURL=PasskeyError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["CODES","Set","TOKEN_RE","ASAUTH_RE","codeForToken","token","undefined","test","has","PasskeyError","Error","constructor","code","message","name","from","error","original","match","exec","stripped","replace","length","exports"],"sourceRoot":"../../src","sources":["PasskeyError.ts"],"mappings":";;;;;;AASA,MAAMA,KAA0B,GAAG,IAAIC,GAAG,CAAmB,CAC3D,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,SAAS,CACV,CAAC;AAEF,MAAMC,QAAQ,GAAG,iCAAiC;AAClD,MAAMC,SAAS,GAAG,cAAc;AAEhC,SAASC,YAAYA,CAACC,KAAyB,EAAoB;EACjE,IAAIA,KAAK,KAAKC,SAAS,EAAE,OAAO,SAAS;EACzC,IAAID,KAAK,KAAK,aAAa,EAAE,OAAO,UAAU;EAC9C,IAAIA,KAAK,KAAK,aAAa,EAAE,OAAO,mBAAmB;EACvD,IAAIF,SAAS,CAACI,IAAI,CAACF,KAAK,CAAC,EAAE,OAAO,SAAS;EAC3C,OAAOL,KAAK,CAACQ,GAAG,CAACH,KAAK,CAAC,GAAIA,KAAK,GAAwB,SAAS;AACnE;AAEO,MAAMI,YAAY,SAASC,KAAK,CAAC;EAGtCC,WAAWA,CAACC,IAAsB,EAAEC,OAAe,EAAE;IACnD,KAAK,CAACA,OAAO,CAAC;IACd,IAAI,CAACC,IAAI,GAAG,cAAc;IAC1B,IAAI,CAACF,IAAI,GAAGA,IAAI;EAClB;;EAEA;EACA,OAAOG,IAAIA,CAACC,KAAc,EAAgB;IACxC,IAAI;MACF,IAAIA,KAAK,YAAYP,YAAY,EAAE,OAAOO,KAAK;MAC/C,MAAMC,QAAQ,GACZD,KAAK,YAAYN,KAAK,GAClBM,KAAK,CAACH,OAAO,GACb,OAAOG,KAAK,KAAK,QAAQ,GACvBA,KAAK,GACL,eAAe;MACvB,MAAME,KAAK,GAAGhB,QAAQ,CAACiB,IAAI,CAACF,QAAQ,CAAC;MACrC,MAAML,IAAI,GAAGR,YAAY,CAACc,KAAK,GAAG,CAAC,CAAC,CAAC;MACrC,MAAME,QAAQ,GAAGF,KAAK,GAAGD,QAAQ,CAACI,OAAO,CAACnB,QAAQ,EAAE,EAAE,CAAC,GAAGe,QAAQ;MAClE,OAAO,IAAIR,YAAY,CAACG,IAAI,EAAEQ,QAAQ,CAACE,MAAM,GAAG,CAAC,GAAGF,QAAQ,GAAGH,QAAQ,CAAC;IAC1E,CAAC,CAAC,MAAM;MACN,OAAO,IAAIR,YAAY,CAAC,SAAS,EAAE,eAAe,CAAC;IACrD;EACF;AACF;AAACc,OAAA,CAAAd,YAAA,GAAAA,YAAA","ignoreList":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "PasskeyError", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _PasskeyError.PasskeyError;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
exports.createPasskey = createPasskey;
|
|
13
|
+
exports.getPasskey = getPasskey;
|
|
14
|
+
exports.isPasskeySupported = isPasskeySupported;
|
|
15
|
+
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
16
|
+
var _PasskeyError = require("./PasskeyError");
|
|
17
|
+
var _response = require("./response");
|
|
18
|
+
var _validate = require("./validate");
|
|
19
|
+
// Deliberately not exported: every caller goes through the helpers and gets typed errors.
|
|
20
|
+
const native = _reactNativeNitroModules.NitroModules.createHybridObject('NitroPasskeys');
|
|
21
|
+
function isPasskeySupported() {
|
|
22
|
+
return native.isSupported;
|
|
23
|
+
}
|
|
24
|
+
async function createPasskey(options) {
|
|
25
|
+
if (!isPasskeySupported()) {
|
|
26
|
+
throw new _PasskeyError.PasskeyError('unsupported', 'Passkeys are not supported on this device');
|
|
27
|
+
}
|
|
28
|
+
const reason = (0, _validate.validateCreationOptions)(options);
|
|
29
|
+
if (reason) throw new _PasskeyError.PasskeyError('invalidOptions', reason);
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = await native.create(JSON.stringify(options));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw _PasskeyError.PasskeyError.from(error);
|
|
35
|
+
}
|
|
36
|
+
return (0, _response.parseRegistrationResponse)(raw);
|
|
37
|
+
}
|
|
38
|
+
async function getPasskey(options) {
|
|
39
|
+
if (!isPasskeySupported()) {
|
|
40
|
+
throw new _PasskeyError.PasskeyError('unsupported', 'Passkeys are not supported on this device');
|
|
41
|
+
}
|
|
42
|
+
const reason = (0, _validate.validateRequestOptions)(options);
|
|
43
|
+
if (reason) throw new _PasskeyError.PasskeyError('invalidOptions', reason);
|
|
44
|
+
let raw;
|
|
45
|
+
try {
|
|
46
|
+
raw = await native.get(JSON.stringify(options));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw _PasskeyError.PasskeyError.from(error);
|
|
49
|
+
}
|
|
50
|
+
return (0, _response.parseAuthenticationResponse)(raw);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNativeNitroModules","require","_PasskeyError","_response","_validate","native","NitroModules","createHybridObject","isPasskeySupported","isSupported","createPasskey","options","PasskeyError","reason","validateCreationOptions","raw","create","JSON","stringify","error","from","parseRegistrationResponse","getPasskey","validateRequestOptions","get","parseAuthenticationResponse"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;AAAA,IAAAA,wBAAA,GAAAC,OAAA;AAEA,IAAAC,aAAA,GAAAD,OAAA;AACA,IAAAE,SAAA,GAAAF,OAAA;AAWA,IAAAG,SAAA,GAAAH,OAAA;AAEA;AACA,MAAMI,MAAM,GAAGC,qCAAY,CAACC,kBAAkB,CAAgB,eAAe,CAAC;AAEvE,SAASC,kBAAkBA,CAAA,EAAY;EAC5C,OAAOH,MAAM,CAACI,WAAW;AAC3B;AAEO,eAAeC,aAAaA,CACjCC,OAA+C,EACZ;EACnC,IAAI,CAACH,kBAAkB,CAAC,CAAC,EAAE;IACzB,MAAM,IAAII,0BAAY,CACpB,aAAa,EACb,2CACF,CAAC;EACH;EACA,MAAMC,MAAM,GAAG,IAAAC,iCAAuB,EAACH,OAAO,CAAC;EAC/C,IAAIE,MAAM,EAAE,MAAM,IAAID,0BAAY,CAAC,gBAAgB,EAAEC,MAAM,CAAC;EAC5D,IAAIE,GAAW;EACf,IAAI;IACFA,GAAG,GAAG,MAAMV,MAAM,CAACW,MAAM,CAACC,IAAI,CAACC,SAAS,CAACP,OAAO,CAAC,CAAC;EACpD,CAAC,CAAC,OAAOQ,KAAK,EAAE;IACd,MAAMP,0BAAY,CAACQ,IAAI,CAACD,KAAK,CAAC;EAChC;EACA,OAAO,IAAAE,mCAAyB,EAACN,GAAG,CAAC;AACvC;AAEO,eAAeO,UAAUA,CAC9BX,OAA8C,EACT;EACrC,IAAI,CAACH,kBAAkB,CAAC,CAAC,EAAE;IACzB,MAAM,IAAII,0BAAY,CACpB,aAAa,EACb,2CACF,CAAC;EACH;EACA,MAAMC,MAAM,GAAG,IAAAU,gCAAsB,EAACZ,OAAO,CAAC;EAC9C,IAAIE,MAAM,EAAE,MAAM,IAAID,0BAAY,CAAC,gBAAgB,EAAEC,MAAM,CAAC;EAC5D,IAAIE,GAAW;EACf,IAAI;IACFA,GAAG,GAAG,MAAMV,MAAM,CAACmB,GAAG,CAACP,IAAI,CAACC,SAAS,CAACP,OAAO,CAAC,CAAC;EACjD,CAAC,CAAC,OAAOQ,KAAK,EAAE;IACd,MAAMP,0BAAY,CAACQ,IAAI,CAACD,KAAK,CAAC;EAChC;EACA,OAAO,IAAAM,qCAA2B,EAACV,GAAG,CAAC;AACzC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.parseAuthenticationResponse = parseAuthenticationResponse;
|
|
7
|
+
exports.parseRegistrationResponse = parseRegistrationResponse;
|
|
8
|
+
var _PasskeyError = require("./PasskeyError");
|
|
9
|
+
var _validate = require("./validate");
|
|
10
|
+
function parse(raw, label, validate) {
|
|
11
|
+
let value;
|
|
12
|
+
try {
|
|
13
|
+
value = JSON.parse(raw);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new _PasskeyError.PasskeyError('unknown', `Malformed ${label} response: not JSON`);
|
|
16
|
+
}
|
|
17
|
+
const reason = validate(value);
|
|
18
|
+
if (reason) throw new _PasskeyError.PasskeyError('unknown', `Malformed ${label} response: ${reason}`);
|
|
19
|
+
const result = value;
|
|
20
|
+
// The only normalisation: nothing else is added, removed or re-encoded.
|
|
21
|
+
result.clientExtensionResults ??= {};
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
function parseRegistrationResponse(raw) {
|
|
25
|
+
return parse(raw, 'registration', _validate.validateRegistrationResponse);
|
|
26
|
+
}
|
|
27
|
+
function parseAuthenticationResponse(raw) {
|
|
28
|
+
return parse(raw, 'authentication', _validate.validateAuthenticationResponse);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_PasskeyError","require","_validate","parse","raw","label","validate","value","JSON","PasskeyError","reason","result","clientExtensionResults","parseRegistrationResponse","validateRegistrationResponse","parseAuthenticationResponse","validateAuthenticationResponse"],"sourceRoot":"../../src","sources":["response.ts"],"mappings":";;;;;;;AAAA,IAAAA,aAAA,GAAAC,OAAA;AAKA,IAAAC,SAAA,GAAAD,OAAA;AAKA,SAASE,KAAKA,CACZC,GAAW,EACXC,KAAwC,EACxCC,QAAgD,EAC7C;EACH,IAAIC,KAAc;EAClB,IAAI;IACFA,KAAK,GAAGC,IAAI,CAACL,KAAK,CAACC,GAAG,CAAC;EACzB,CAAC,CAAC,MAAM;IACN,MAAM,IAAIK,0BAAY,CAAC,SAAS,EAAE,aAAaJ,KAAK,qBAAqB,CAAC;EAC5E;EACA,MAAMK,MAAM,GAAGJ,QAAQ,CAACC,KAAK,CAAC;EAC9B,IAAIG,MAAM,EACR,MAAM,IAAID,0BAAY,CAAC,SAAS,EAAE,aAAaJ,KAAK,cAAcK,MAAM,EAAE,CAAC;EAC7E,MAAMC,MAAM,GAAGJ,KAAU;EACzB;EACAI,MAAM,CAACC,sBAAsB,KAAK,CAAC,CAAC;EACpC,OAAOD,MAAM;AACf;AAEO,SAASE,yBAAyBA,CACvCT,GAAW,EACe;EAC1B,OAAOD,KAAK,CACVC,GAAG,EACH,cAAc,EACdU,sCACF,CAAC;AACH;AAEO,SAASC,2BAA2BA,CACzCX,GAAW,EACiB;EAC5B,OAAOD,KAAK,CACVC,GAAG,EACH,gBAAgB,EAChBY,wCACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["specs/nitro-passkeys.nitro.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["types.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.base64URLByteLength = base64URLByteLength;
|
|
7
|
+
exports.isBase64URL = isBase64URL;
|
|
8
|
+
exports.validateAuthenticationResponse = validateAuthenticationResponse;
|
|
9
|
+
exports.validateCreationOptions = validateCreationOptions;
|
|
10
|
+
exports.validateRegistrationResponse = validateRegistrationResponse;
|
|
11
|
+
exports.validateRequestOptions = validateRequestOptions;
|
|
12
|
+
const BASE64URL_RE = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
13
|
+
function unpaddedLength(value) {
|
|
14
|
+
let end = value.length;
|
|
15
|
+
while (end > 0 && value[end - 1] === '=') end--;
|
|
16
|
+
return end;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True for a non-empty base64url string with optional `=` padding. Rejects `+`, `/`, whitespace,
|
|
21
|
+
* impossible lengths (unpadded length % 4 === 1) and padding that does not complete a 4-char group.
|
|
22
|
+
*/
|
|
23
|
+
function isBase64URL(value) {
|
|
24
|
+
if (typeof value !== 'string' || !BASE64URL_RE.test(value)) return false;
|
|
25
|
+
const unpadded = unpaddedLength(value);
|
|
26
|
+
if (unpadded === 0 || unpadded % 4 === 1) return false;
|
|
27
|
+
if (unpadded !== value.length && value.length % 4 !== 0) return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Number of bytes a valid base64url string decodes to. */
|
|
32
|
+
function base64URLByteLength(value) {
|
|
33
|
+
return Math.floor(unpaddedLength(value) * 3 / 4);
|
|
34
|
+
}
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function isNonEmptyString(value) {
|
|
39
|
+
return typeof value === 'string' && value.length > 0;
|
|
40
|
+
}
|
|
41
|
+
function validateDescriptors(value, field) {
|
|
42
|
+
if (value === undefined) return undefined;
|
|
43
|
+
if (!Array.isArray(value)) return `${field} must be an array`;
|
|
44
|
+
for (const [index, entry] of value.entries()) {
|
|
45
|
+
if (!isRecord(entry) || !isBase64URL(entry['id'])) {
|
|
46
|
+
return `${field}[${index}].id is not valid base64url`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Returns a reason for the first failing rule, or `undefined` when the options are valid. */
|
|
53
|
+
function validateCreationOptions(options) {
|
|
54
|
+
if (!isRecord(options)) return 'options must be an object';
|
|
55
|
+
const {
|
|
56
|
+
rp,
|
|
57
|
+
user,
|
|
58
|
+
challenge,
|
|
59
|
+
excludeCredentials,
|
|
60
|
+
pubKeyCredParams
|
|
61
|
+
} = options;
|
|
62
|
+
if (!isRecord(rp)) return 'rp must be an object';
|
|
63
|
+
if (!isNonEmptyString(rp['id'])) return 'rp.id is required';
|
|
64
|
+
if (!isNonEmptyString(challenge)) return 'challenge is required';
|
|
65
|
+
if (!isBase64URL(challenge)) return 'challenge is not valid base64url';
|
|
66
|
+
if (!isRecord(user)) return 'user must be an object';
|
|
67
|
+
if (!isNonEmptyString(user['id'])) return 'user.id is required';
|
|
68
|
+
if (!isNonEmptyString(user['name'])) return 'user.name is required';
|
|
69
|
+
if (!isBase64URL(user['id'])) return 'user.id is not valid base64url';
|
|
70
|
+
const userIdBytes = base64URLByteLength(user['id']);
|
|
71
|
+
if (userIdBytes < 1 || userIdBytes > 64) return 'user.id must decode to 1-64 bytes';
|
|
72
|
+
const excludeReason = validateDescriptors(excludeCredentials, 'excludeCredentials');
|
|
73
|
+
if (excludeReason) return excludeReason;
|
|
74
|
+
if (pubKeyCredParams !== undefined) {
|
|
75
|
+
if (!Array.isArray(pubKeyCredParams)) return 'pubKeyCredParams must be an array';
|
|
76
|
+
if (pubKeyCredParams.length > 0 && !pubKeyCredParams.some(param => isRecord(param) && param['alg'] === -7)) {
|
|
77
|
+
return 'pubKeyCredParams must include ES256 (alg -7)';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Returns a reason for the first failing rule, or `undefined` when the options are valid. */
|
|
84
|
+
function validateRequestOptions(options) {
|
|
85
|
+
if (!isRecord(options)) return 'options must be an object';
|
|
86
|
+
if (!isNonEmptyString(options['rpId'])) return 'rpId is required';
|
|
87
|
+
if (!isNonEmptyString(options['challenge'])) return 'challenge is required';
|
|
88
|
+
if (!isBase64URL(options['challenge'])) return 'challenge is not valid base64url';
|
|
89
|
+
return validateDescriptors(options['allowCredentials'], 'allowCredentials');
|
|
90
|
+
}
|
|
91
|
+
function validateCommonResponse(value) {
|
|
92
|
+
if (!isRecord(value)) return 'response must be an object';
|
|
93
|
+
if (!isNonEmptyString(value['id'])) return 'id is missing';
|
|
94
|
+
if (!isNonEmptyString(value['rawId'])) return 'rawId is missing';
|
|
95
|
+
if (value['id'] !== value['rawId']) return 'id does not equal rawId';
|
|
96
|
+
if (value['type'] !== 'public-key') return "type must be 'public-key'";
|
|
97
|
+
if (!isRecord(value['response'])) return 'response.response is missing';
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
function missingFields(response, fields) {
|
|
101
|
+
for (const field of fields) {
|
|
102
|
+
if (!isNonEmptyString(response[field])) return `response.${field} is missing`;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
function validateRegistrationResponse(value) {
|
|
107
|
+
const common = validateCommonResponse(value);
|
|
108
|
+
if (common) return common;
|
|
109
|
+
const response = value.response;
|
|
110
|
+
return missingFields(response, ['clientDataJSON', 'attestationObject']);
|
|
111
|
+
}
|
|
112
|
+
function validateAuthenticationResponse(value) {
|
|
113
|
+
const common = validateCommonResponse(value);
|
|
114
|
+
if (common) return common;
|
|
115
|
+
const response = value.response;
|
|
116
|
+
return missingFields(response, ['clientDataJSON', 'authenticatorData', 'signature']);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["BASE64URL_RE","unpaddedLength","value","end","length","isBase64URL","test","unpadded","base64URLByteLength","Math","floor","isRecord","Array","isArray","isNonEmptyString","validateDescriptors","field","undefined","index","entry","entries","validateCreationOptions","options","rp","user","challenge","excludeCredentials","pubKeyCredParams","userIdBytes","excludeReason","some","param","validateRequestOptions","validateCommonResponse","missingFields","response","fields","validateRegistrationResponse","common","validateAuthenticationResponse"],"sourceRoot":"../../src","sources":["validate.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const CODES = new Set(['canceled', 'noCredentials', 'alreadyRegistered', 'domainNotAssociated', 'unsupported', 'invalidOptions', 'unknown']);
|
|
4
|
+
const TOKEN_RE = /\[Passkeys:([A-Za-z0-9-]+)\]\s?/;
|
|
5
|
+
const ASAUTH_RE = /^asauth-\d+$/;
|
|
6
|
+
function codeForToken(token) {
|
|
7
|
+
if (token === undefined) return 'unknown';
|
|
8
|
+
if (token === 'asauth-1001') return 'canceled';
|
|
9
|
+
if (token === 'asauth-1006') return 'alreadyRegistered';
|
|
10
|
+
if (ASAUTH_RE.test(token)) return 'unknown';
|
|
11
|
+
return CODES.has(token) ? token : 'unknown';
|
|
12
|
+
}
|
|
13
|
+
export class PasskeyError extends Error {
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'PasskeyError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Normalises any thrown value into a `PasskeyError`. Never throws. */
|
|
21
|
+
static from(error) {
|
|
22
|
+
try {
|
|
23
|
+
if (error instanceof PasskeyError) return error;
|
|
24
|
+
const original = error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unknown error';
|
|
25
|
+
const match = TOKEN_RE.exec(original);
|
|
26
|
+
const code = codeForToken(match?.[1]);
|
|
27
|
+
const stripped = match ? original.replace(TOKEN_RE, '') : original;
|
|
28
|
+
return new PasskeyError(code, stripped.length > 0 ? stripped : original);
|
|
29
|
+
} catch {
|
|
30
|
+
return new PasskeyError('unknown', 'Unknown error');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=PasskeyError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["CODES","Set","TOKEN_RE","ASAUTH_RE","codeForToken","token","undefined","test","has","PasskeyError","Error","constructor","code","message","name","from","error","original","match","exec","stripped","replace","length"],"sourceRoot":"../../src","sources":["PasskeyError.ts"],"mappings":";;AASA,MAAMA,KAA0B,GAAG,IAAIC,GAAG,CAAmB,CAC3D,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,SAAS,CACV,CAAC;AAEF,MAAMC,QAAQ,GAAG,iCAAiC;AAClD,MAAMC,SAAS,GAAG,cAAc;AAEhC,SAASC,YAAYA,CAACC,KAAyB,EAAoB;EACjE,IAAIA,KAAK,KAAKC,SAAS,EAAE,OAAO,SAAS;EACzC,IAAID,KAAK,KAAK,aAAa,EAAE,OAAO,UAAU;EAC9C,IAAIA,KAAK,KAAK,aAAa,EAAE,OAAO,mBAAmB;EACvD,IAAIF,SAAS,CAACI,IAAI,CAACF,KAAK,CAAC,EAAE,OAAO,SAAS;EAC3C,OAAOL,KAAK,CAACQ,GAAG,CAACH,KAAK,CAAC,GAAIA,KAAK,GAAwB,SAAS;AACnE;AAEA,OAAO,MAAMI,YAAY,SAASC,KAAK,CAAC;EAGtCC,WAAWA,CAACC,IAAsB,EAAEC,OAAe,EAAE;IACnD,KAAK,CAACA,OAAO,CAAC;IACd,IAAI,CAACC,IAAI,GAAG,cAAc;IAC1B,IAAI,CAACF,IAAI,GAAGA,IAAI;EAClB;;EAEA;EACA,OAAOG,IAAIA,CAACC,KAAc,EAAgB;IACxC,IAAI;MACF,IAAIA,KAAK,YAAYP,YAAY,EAAE,OAAOO,KAAK;MAC/C,MAAMC,QAAQ,GACZD,KAAK,YAAYN,KAAK,GAClBM,KAAK,CAACH,OAAO,GACb,OAAOG,KAAK,KAAK,QAAQ,GACvBA,KAAK,GACL,eAAe;MACvB,MAAME,KAAK,GAAGhB,QAAQ,CAACiB,IAAI,CAACF,QAAQ,CAAC;MACrC,MAAML,IAAI,GAAGR,YAAY,CAACc,KAAK,GAAG,CAAC,CAAC,CAAC;MACrC,MAAME,QAAQ,GAAGF,KAAK,GAAGD,QAAQ,CAACI,OAAO,CAACnB,QAAQ,EAAE,EAAE,CAAC,GAAGe,QAAQ;MAClE,OAAO,IAAIR,YAAY,CAACG,IAAI,EAAEQ,QAAQ,CAACE,MAAM,GAAG,CAAC,GAAGF,QAAQ,GAAGH,QAAQ,CAAC;IAC1E,CAAC,CAAC,MAAM;MACN,OAAO,IAAIR,YAAY,CAAC,SAAS,EAAE,eAAe,CAAC;IACrD;EACF;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
4
|
+
import { PasskeyError } from './PasskeyError';
|
|
5
|
+
import { parseAuthenticationResponse, parseRegistrationResponse } from './response';
|
|
6
|
+
import { validateCreationOptions, validateRequestOptions } from './validate';
|
|
7
|
+
|
|
8
|
+
// Deliberately not exported: every caller goes through the helpers and gets typed errors.
|
|
9
|
+
const native = NitroModules.createHybridObject('NitroPasskeys');
|
|
10
|
+
export function isPasskeySupported() {
|
|
11
|
+
return native.isSupported;
|
|
12
|
+
}
|
|
13
|
+
export async function createPasskey(options) {
|
|
14
|
+
if (!isPasskeySupported()) {
|
|
15
|
+
throw new PasskeyError('unsupported', 'Passkeys are not supported on this device');
|
|
16
|
+
}
|
|
17
|
+
const reason = validateCreationOptions(options);
|
|
18
|
+
if (reason) throw new PasskeyError('invalidOptions', reason);
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = await native.create(JSON.stringify(options));
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw PasskeyError.from(error);
|
|
24
|
+
}
|
|
25
|
+
return parseRegistrationResponse(raw);
|
|
26
|
+
}
|
|
27
|
+
export async function getPasskey(options) {
|
|
28
|
+
if (!isPasskeySupported()) {
|
|
29
|
+
throw new PasskeyError('unsupported', 'Passkeys are not supported on this device');
|
|
30
|
+
}
|
|
31
|
+
const reason = validateRequestOptions(options);
|
|
32
|
+
if (reason) throw new PasskeyError('invalidOptions', reason);
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = await native.get(JSON.stringify(options));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw PasskeyError.from(error);
|
|
38
|
+
}
|
|
39
|
+
return parseAuthenticationResponse(raw);
|
|
40
|
+
}
|
|
41
|
+
export { PasskeyError } from './PasskeyError';
|
|
42
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NitroModules","PasskeyError","parseAuthenticationResponse","parseRegistrationResponse","validateCreationOptions","validateRequestOptions","native","createHybridObject","isPasskeySupported","isSupported","createPasskey","options","reason","raw","create","JSON","stringify","error","from","getPasskey","get"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,4BAA4B;AAEzD,SAASC,YAAY,QAAQ,gBAAgB;AAC7C,SACEC,2BAA2B,EAC3BC,yBAAyB,QACpB,YAAY;AAQnB,SAASC,uBAAuB,EAAEC,sBAAsB,QAAQ,YAAY;;AAE5E;AACA,MAAMC,MAAM,GAAGN,YAAY,CAACO,kBAAkB,CAAgB,eAAe,CAAC;AAE9E,OAAO,SAASC,kBAAkBA,CAAA,EAAY;EAC5C,OAAOF,MAAM,CAACG,WAAW;AAC3B;AAEA,OAAO,eAAeC,aAAaA,CACjCC,OAA+C,EACZ;EACnC,IAAI,CAACH,kBAAkB,CAAC,CAAC,EAAE;IACzB,MAAM,IAAIP,YAAY,CACpB,aAAa,EACb,2CACF,CAAC;EACH;EACA,MAAMW,MAAM,GAAGR,uBAAuB,CAACO,OAAO,CAAC;EAC/C,IAAIC,MAAM,EAAE,MAAM,IAAIX,YAAY,CAAC,gBAAgB,EAAEW,MAAM,CAAC;EAC5D,IAAIC,GAAW;EACf,IAAI;IACFA,GAAG,GAAG,MAAMP,MAAM,CAACQ,MAAM,CAACC,IAAI,CAACC,SAAS,CAACL,OAAO,CAAC,CAAC;EACpD,CAAC,CAAC,OAAOM,KAAK,EAAE;IACd,MAAMhB,YAAY,CAACiB,IAAI,CAACD,KAAK,CAAC;EAChC;EACA,OAAOd,yBAAyB,CAACU,GAAG,CAAC;AACvC;AAEA,OAAO,eAAeM,UAAUA,CAC9BR,OAA8C,EACT;EACrC,IAAI,CAACH,kBAAkB,CAAC,CAAC,EAAE;IACzB,MAAM,IAAIP,YAAY,CACpB,aAAa,EACb,2CACF,CAAC;EACH;EACA,MAAMW,MAAM,GAAGP,sBAAsB,CAACM,OAAO,CAAC;EAC9C,IAAIC,MAAM,EAAE,MAAM,IAAIX,YAAY,CAAC,gBAAgB,EAAEW,MAAM,CAAC;EAC5D,IAAIC,GAAW;EACf,IAAI;IACFA,GAAG,GAAG,MAAMP,MAAM,CAACc,GAAG,CAACL,IAAI,CAACC,SAAS,CAACL,OAAO,CAAC,CAAC;EACjD,CAAC,CAAC,OAAOM,KAAK,EAAE;IACd,MAAMhB,YAAY,CAACiB,IAAI,CAACD,KAAK,CAAC;EAChC;EACA,OAAOf,2BAA2B,CAACW,GAAG,CAAC;AACzC;AAEA,SAASZ,YAAY,QAAQ,gBAAgB","ignoreList":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { PasskeyError } from './PasskeyError';
|
|
4
|
+
import { validateAuthenticationResponse, validateRegistrationResponse } from './validate';
|
|
5
|
+
function parse(raw, label, validate) {
|
|
6
|
+
let value;
|
|
7
|
+
try {
|
|
8
|
+
value = JSON.parse(raw);
|
|
9
|
+
} catch {
|
|
10
|
+
throw new PasskeyError('unknown', `Malformed ${label} response: not JSON`);
|
|
11
|
+
}
|
|
12
|
+
const reason = validate(value);
|
|
13
|
+
if (reason) throw new PasskeyError('unknown', `Malformed ${label} response: ${reason}`);
|
|
14
|
+
const result = value;
|
|
15
|
+
// The only normalisation: nothing else is added, removed or re-encoded.
|
|
16
|
+
result.clientExtensionResults ??= {};
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
export function parseRegistrationResponse(raw) {
|
|
20
|
+
return parse(raw, 'registration', validateRegistrationResponse);
|
|
21
|
+
}
|
|
22
|
+
export function parseAuthenticationResponse(raw) {
|
|
23
|
+
return parse(raw, 'authentication', validateAuthenticationResponse);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["PasskeyError","validateAuthenticationResponse","validateRegistrationResponse","parse","raw","label","validate","value","JSON","reason","result","clientExtensionResults","parseRegistrationResponse","parseAuthenticationResponse"],"sourceRoot":"../../src","sources":["response.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,gBAAgB;AAK7C,SACEC,8BAA8B,EAC9BC,4BAA4B,QACvB,YAAY;AAEnB,SAASC,KAAKA,CACZC,GAAW,EACXC,KAAwC,EACxCC,QAAgD,EAC7C;EACH,IAAIC,KAAc;EAClB,IAAI;IACFA,KAAK,GAAGC,IAAI,CAACL,KAAK,CAACC,GAAG,CAAC;EACzB,CAAC,CAAC,MAAM;IACN,MAAM,IAAIJ,YAAY,CAAC,SAAS,EAAE,aAAaK,KAAK,qBAAqB,CAAC;EAC5E;EACA,MAAMI,MAAM,GAAGH,QAAQ,CAACC,KAAK,CAAC;EAC9B,IAAIE,MAAM,EACR,MAAM,IAAIT,YAAY,CAAC,SAAS,EAAE,aAAaK,KAAK,cAAcI,MAAM,EAAE,CAAC;EAC7E,MAAMC,MAAM,GAAGH,KAAU;EACzB;EACAG,MAAM,CAACC,sBAAsB,KAAK,CAAC,CAAC;EACpC,OAAOD,MAAM;AACf;AAEA,OAAO,SAASE,yBAAyBA,CACvCR,GAAW,EACe;EAC1B,OAAOD,KAAK,CACVC,GAAG,EACH,cAAc,EACdF,4BACF,CAAC;AACH;AAEA,OAAO,SAASW,2BAA2BA,CACzCT,GAAW,EACiB;EAC5B,OAAOD,KAAK,CACVC,GAAG,EACH,gBAAgB,EAChBH,8BACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["specs/nitro-passkeys.nitro.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["types.ts"],"mappings":"","ignoreList":[]}
|