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
package/src/types.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export type Base64URLString = string;
|
|
2
|
+
export type PublicKeyCredentialType = 'public-key';
|
|
3
|
+
export type COSEAlgorithmIdentifier = number;
|
|
4
|
+
export type AuthenticatorTransport =
|
|
5
|
+
| 'ble'
|
|
6
|
+
| 'hybrid'
|
|
7
|
+
| 'internal'
|
|
8
|
+
| 'nfc'
|
|
9
|
+
| 'usb';
|
|
10
|
+
export type AuthenticatorAttachment = 'cross-platform' | 'platform';
|
|
11
|
+
export type UserVerificationRequirement =
|
|
12
|
+
| 'discouraged'
|
|
13
|
+
| 'preferred'
|
|
14
|
+
| 'required';
|
|
15
|
+
export type ResidentKeyRequirement = 'discouraged' | 'preferred' | 'required';
|
|
16
|
+
export type AttestationConveyancePreference =
|
|
17
|
+
| 'direct'
|
|
18
|
+
| 'enterprise'
|
|
19
|
+
| 'indirect'
|
|
20
|
+
| 'none';
|
|
21
|
+
export type AttestationFormat =
|
|
22
|
+
| 'fido-u2f'
|
|
23
|
+
| 'packed'
|
|
24
|
+
| 'android-safetynet'
|
|
25
|
+
| 'android-key'
|
|
26
|
+
| 'tpm'
|
|
27
|
+
| 'apple'
|
|
28
|
+
| 'none';
|
|
29
|
+
export type PublicKeyCredentialHint =
|
|
30
|
+
| 'hybrid'
|
|
31
|
+
| 'security-key'
|
|
32
|
+
| 'client-device';
|
|
33
|
+
|
|
34
|
+
export interface PublicKeyCredentialRpEntity {
|
|
35
|
+
name: string;
|
|
36
|
+
id?: string; // optional in the type; REQUIRED at runtime by this module (§3.4)
|
|
37
|
+
}
|
|
38
|
+
export interface PublicKeyCredentialUserEntityJSON {
|
|
39
|
+
id: Base64URLString; // base64url of the user-handle bytes (simplewebauthn ≥10)
|
|
40
|
+
name: string;
|
|
41
|
+
displayName: string;
|
|
42
|
+
}
|
|
43
|
+
export interface PublicKeyCredentialParameters {
|
|
44
|
+
alg: COSEAlgorithmIdentifier;
|
|
45
|
+
type: PublicKeyCredentialType;
|
|
46
|
+
}
|
|
47
|
+
export interface PublicKeyCredentialDescriptorJSON {
|
|
48
|
+
id: Base64URLString;
|
|
49
|
+
type: string; // simplewebauthn types this as string
|
|
50
|
+
transports?: string[]; // simplewebauthn types this as string[]
|
|
51
|
+
}
|
|
52
|
+
export interface AuthenticatorSelectionCriteria {
|
|
53
|
+
authenticatorAttachment?: AuthenticatorAttachment;
|
|
54
|
+
requireResidentKey?: boolean;
|
|
55
|
+
residentKey?: ResidentKeyRequirement;
|
|
56
|
+
userVerification?: UserVerificationRequirement;
|
|
57
|
+
}
|
|
58
|
+
export interface AuthenticationExtensionsClientInputs {
|
|
59
|
+
appid?: string;
|
|
60
|
+
credProps?: boolean;
|
|
61
|
+
hmacCreateSecret?: boolean;
|
|
62
|
+
minPinLength?: boolean;
|
|
63
|
+
prf?: unknown; // deviation: simplewebauthn uses BufferSource values, which JSON cannot carry
|
|
64
|
+
}
|
|
65
|
+
export interface AuthenticationExtensionsClientOutputs {
|
|
66
|
+
appid?: boolean;
|
|
67
|
+
credProps?: { rk?: boolean };
|
|
68
|
+
hmacCreateSecret?: boolean;
|
|
69
|
+
prf?: { enabled?: boolean }; // deviation: `results` (BufferSource) omitted
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface PublicKeyCredentialCreationOptionsJSON {
|
|
73
|
+
rp: PublicKeyCredentialRpEntity;
|
|
74
|
+
user: PublicKeyCredentialUserEntityJSON;
|
|
75
|
+
challenge: Base64URLString;
|
|
76
|
+
pubKeyCredParams: PublicKeyCredentialParameters[];
|
|
77
|
+
timeout?: number;
|
|
78
|
+
excludeCredentials?: PublicKeyCredentialDescriptorJSON[];
|
|
79
|
+
authenticatorSelection?: AuthenticatorSelectionCriteria;
|
|
80
|
+
hints?: PublicKeyCredentialHint[];
|
|
81
|
+
attestation?: AttestationConveyancePreference;
|
|
82
|
+
attestationFormats?: AttestationFormat[];
|
|
83
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
84
|
+
}
|
|
85
|
+
export interface PublicKeyCredentialRequestOptionsJSON {
|
|
86
|
+
challenge: Base64URLString;
|
|
87
|
+
timeout?: number;
|
|
88
|
+
rpId?: string; // optional in the type; REQUIRED at runtime by this module (§3.4)
|
|
89
|
+
allowCredentials?: PublicKeyCredentialDescriptorJSON[];
|
|
90
|
+
userVerification?: UserVerificationRequirement;
|
|
91
|
+
hints?: PublicKeyCredentialHint[];
|
|
92
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface AuthenticatorAttestationResponseJSON {
|
|
96
|
+
clientDataJSON: Base64URLString;
|
|
97
|
+
attestationObject: Base64URLString;
|
|
98
|
+
authenticatorData?: Base64URLString;
|
|
99
|
+
transports?: AuthenticatorTransport[]; // narrower than simplewebauthn's string[] — assigns fine
|
|
100
|
+
publicKeyAlgorithm?: COSEAlgorithmIdentifier;
|
|
101
|
+
publicKey?: Base64URLString;
|
|
102
|
+
}
|
|
103
|
+
export interface AuthenticatorAssertionResponseJSON {
|
|
104
|
+
clientDataJSON: Base64URLString;
|
|
105
|
+
authenticatorData: Base64URLString;
|
|
106
|
+
signature: Base64URLString;
|
|
107
|
+
userHandle?: Base64URLString;
|
|
108
|
+
}
|
|
109
|
+
export interface RegistrationResponseJSON {
|
|
110
|
+
id: Base64URLString;
|
|
111
|
+
rawId: Base64URLString;
|
|
112
|
+
response: AuthenticatorAttestationResponseJSON;
|
|
113
|
+
authenticatorAttachment?: AuthenticatorAttachment;
|
|
114
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
115
|
+
type: PublicKeyCredentialType;
|
|
116
|
+
}
|
|
117
|
+
export interface AuthenticationResponseJSON {
|
|
118
|
+
id: Base64URLString;
|
|
119
|
+
rawId: Base64URLString;
|
|
120
|
+
response: AuthenticatorAssertionResponseJSON;
|
|
121
|
+
authenticatorAttachment?: AuthenticatorAttachment;
|
|
122
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
123
|
+
type: PublicKeyCredentialType;
|
|
124
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const BASE64URL_RE = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
2
|
+
|
|
3
|
+
function unpaddedLength(value: string): number {
|
|
4
|
+
let end = value.length;
|
|
5
|
+
while (end > 0 && value[end - 1] === '=') end--;
|
|
6
|
+
return end;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* True for a non-empty base64url string with optional `=` padding. Rejects `+`, `/`, whitespace,
|
|
11
|
+
* impossible lengths (unpadded length % 4 === 1) and padding that does not complete a 4-char group.
|
|
12
|
+
*/
|
|
13
|
+
export function isBase64URL(value: unknown): value is string {
|
|
14
|
+
if (typeof value !== 'string' || !BASE64URL_RE.test(value)) return false;
|
|
15
|
+
const unpadded = unpaddedLength(value);
|
|
16
|
+
if (unpadded === 0 || unpadded % 4 === 1) return false;
|
|
17
|
+
if (unpadded !== value.length && value.length % 4 !== 0) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Number of bytes a valid base64url string decodes to. */
|
|
22
|
+
export function base64URLByteLength(value: string): number {
|
|
23
|
+
return Math.floor((unpaddedLength(value) * 3) / 4);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
31
|
+
return typeof value === 'string' && value.length > 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateDescriptors(
|
|
35
|
+
value: unknown,
|
|
36
|
+
field: string,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
if (value === undefined) return undefined;
|
|
39
|
+
if (!Array.isArray(value)) return `${field} must be an array`;
|
|
40
|
+
for (const [index, entry] of value.entries()) {
|
|
41
|
+
if (!isRecord(entry) || !isBase64URL(entry['id'])) {
|
|
42
|
+
return `${field}[${index}].id is not valid base64url`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Returns a reason for the first failing rule, or `undefined` when the options are valid. */
|
|
49
|
+
export function validateCreationOptions(options: unknown): string | undefined {
|
|
50
|
+
if (!isRecord(options)) return 'options must be an object';
|
|
51
|
+
const { rp, user, challenge, excludeCredentials, pubKeyCredParams } = options;
|
|
52
|
+
if (!isRecord(rp)) return 'rp must be an object';
|
|
53
|
+
if (!isNonEmptyString(rp['id'])) return 'rp.id is required';
|
|
54
|
+
if (!isNonEmptyString(challenge)) return 'challenge is required';
|
|
55
|
+
if (!isBase64URL(challenge)) return 'challenge is not valid base64url';
|
|
56
|
+
if (!isRecord(user)) return 'user must be an object';
|
|
57
|
+
if (!isNonEmptyString(user['id'])) return 'user.id is required';
|
|
58
|
+
if (!isNonEmptyString(user['name'])) return 'user.name is required';
|
|
59
|
+
if (!isBase64URL(user['id'])) return 'user.id is not valid base64url';
|
|
60
|
+
const userIdBytes = base64URLByteLength(user['id']);
|
|
61
|
+
if (userIdBytes < 1 || userIdBytes > 64)
|
|
62
|
+
return 'user.id must decode to 1-64 bytes';
|
|
63
|
+
const excludeReason = validateDescriptors(
|
|
64
|
+
excludeCredentials,
|
|
65
|
+
'excludeCredentials',
|
|
66
|
+
);
|
|
67
|
+
if (excludeReason) return excludeReason;
|
|
68
|
+
if (pubKeyCredParams !== undefined) {
|
|
69
|
+
if (!Array.isArray(pubKeyCredParams))
|
|
70
|
+
return 'pubKeyCredParams must be an array';
|
|
71
|
+
if (
|
|
72
|
+
pubKeyCredParams.length > 0 &&
|
|
73
|
+
!pubKeyCredParams.some((param) => isRecord(param) && param['alg'] === -7)
|
|
74
|
+
) {
|
|
75
|
+
return 'pubKeyCredParams must include ES256 (alg -7)';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Returns a reason for the first failing rule, or `undefined` when the options are valid. */
|
|
82
|
+
export function validateRequestOptions(options: unknown): string | undefined {
|
|
83
|
+
if (!isRecord(options)) return 'options must be an object';
|
|
84
|
+
if (!isNonEmptyString(options['rpId'])) return 'rpId is required';
|
|
85
|
+
if (!isNonEmptyString(options['challenge'])) return 'challenge is required';
|
|
86
|
+
if (!isBase64URL(options['challenge']))
|
|
87
|
+
return 'challenge is not valid base64url';
|
|
88
|
+
return validateDescriptors(options['allowCredentials'], 'allowCredentials');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validateCommonResponse(value: unknown): string | undefined {
|
|
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
|
+
|
|
101
|
+
function missingFields(
|
|
102
|
+
response: Record<string, unknown>,
|
|
103
|
+
fields: string[],
|
|
104
|
+
): string | undefined {
|
|
105
|
+
for (const field of fields) {
|
|
106
|
+
if (!isNonEmptyString(response[field]))
|
|
107
|
+
return `response.${field} is missing`;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function validateRegistrationResponse(
|
|
113
|
+
value: unknown,
|
|
114
|
+
): string | undefined {
|
|
115
|
+
const common = validateCommonResponse(value);
|
|
116
|
+
if (common) return common;
|
|
117
|
+
const response = (value as { response: Record<string, unknown> }).response;
|
|
118
|
+
return missingFields(response, ['clientDataJSON', 'attestationObject']);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function validateAuthenticationResponse(
|
|
122
|
+
value: unknown,
|
|
123
|
+
): string | undefined {
|
|
124
|
+
const common = validateCommonResponse(value);
|
|
125
|
+
if (common) return common;
|
|
126
|
+
const response = (value as { response: Record<string, unknown> }).response;
|
|
127
|
+
return missingFields(response, [
|
|
128
|
+
'clientDataJSON',
|
|
129
|
+
'authenticatorData',
|
|
130
|
+
'signature',
|
|
131
|
+
]);
|
|
132
|
+
}
|