@nokinc-flur/sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/index.d.ts +61 -2
- package/dist/index.js +59 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,43 @@ const completed = await client.onboardingComplete({
|
|
|
40
40
|
// completed: { sessionToken, userId, restricted, risk_reasons }
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Auth + device security methods
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const registered = await client.registerDevice({
|
|
47
|
+
userId: completed.userId,
|
|
48
|
+
appInstanceId: "app-instance-1",
|
|
49
|
+
platform: "ios",
|
|
50
|
+
networkSignals: { ip: "1.2.3.4" },
|
|
51
|
+
});
|
|
52
|
+
// { deviceId, fingerprintHash, driftScore, trustState, stepUpRequired }
|
|
53
|
+
|
|
54
|
+
const refreshed = await client.authRefresh({
|
|
55
|
+
userId: completed.userId,
|
|
56
|
+
refreshToken: completed.sessionToken,
|
|
57
|
+
appInstanceId: "app-instance-1",
|
|
58
|
+
fingerprintHash: registered.fingerprintHash,
|
|
59
|
+
});
|
|
60
|
+
// { refreshToken, stepUpRequired }
|
|
61
|
+
|
|
62
|
+
await client.pinSet({ userId: completed.userId, pin: "123456" });
|
|
63
|
+
await client.pinVerify({ userId: completed.userId, pin: "123456" });
|
|
64
|
+
await client.authLogout({ userId: completed.userId, refreshToken: refreshed.refreshToken });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Error code mapping
|
|
68
|
+
|
|
69
|
+
SDK maps backend error payload `code` into typed `FlurError.code` when available:
|
|
70
|
+
- `TOKEN_REPLAYED`
|
|
71
|
+
- `SESSION_MISMATCH`
|
|
72
|
+
- `PIN_INVALID`
|
|
73
|
+
- `PIN_LOCKED`
|
|
74
|
+
- `PIN_NOT_SET`
|
|
75
|
+
- `STEP_UP_REQUIRED`
|
|
76
|
+
|
|
77
|
+
Fallback codes remain:
|
|
78
|
+
- `HTTP_ERROR`, `NETWORK_ERROR`, `TIMEOUT`, `UNKNOWN`
|
|
79
|
+
|
|
43
80
|
## Lean prod release workflow (one-person team)
|
|
44
81
|
|
|
45
82
|
PowerShell script (Windows):
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ type FlurClientOptions = {
|
|
|
6
6
|
type OnboardingFallback = "SILENT_AUTH" | "OTP";
|
|
7
7
|
type OnboardingStartInput = {
|
|
8
8
|
phoneE164: string;
|
|
9
|
+
firstName?: string;
|
|
10
|
+
lastName?: string;
|
|
9
11
|
};
|
|
10
12
|
type OnboardingStartResponse = {
|
|
11
13
|
requestId: string;
|
|
@@ -17,12 +19,58 @@ type OnboardingRiskReason = "SIM_SWAP_RECENT" | "ROAMING" | "CARRIER_CHANGED";
|
|
|
17
19
|
type OnboardingCompleteInput = {
|
|
18
20
|
requestId: string;
|
|
19
21
|
code: string;
|
|
22
|
+
appInstanceId?: string;
|
|
23
|
+
fingerprintHash?: string;
|
|
20
24
|
};
|
|
21
25
|
type OnboardingCompleteResponse = {
|
|
22
26
|
sessionToken: string;
|
|
23
27
|
userId: string;
|
|
24
28
|
restricted: boolean;
|
|
25
29
|
risk_reasons: OnboardingRiskReason[];
|
|
30
|
+
stepUpRequired?: boolean;
|
|
31
|
+
riskStatus?: "ok" | "unavailable";
|
|
32
|
+
};
|
|
33
|
+
type RegisterDeviceInput = {
|
|
34
|
+
userId: string;
|
|
35
|
+
appInstanceId: string;
|
|
36
|
+
platform: string;
|
|
37
|
+
model?: string;
|
|
38
|
+
networkSignals: {
|
|
39
|
+
ip: string;
|
|
40
|
+
asn?: number;
|
|
41
|
+
country?: string;
|
|
42
|
+
carrier?: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
type DeviceTrustState = "TRUSTED_PRIMARY" | "TRUSTED_SECONDARY" | "UNVERIFIED";
|
|
46
|
+
type RegisterDeviceResponse = {
|
|
47
|
+
deviceId: string;
|
|
48
|
+
fingerprintHash: string;
|
|
49
|
+
driftScore: number;
|
|
50
|
+
trustState: DeviceTrustState;
|
|
51
|
+
stepUpRequired: boolean;
|
|
52
|
+
};
|
|
53
|
+
type AuthRefreshInput = {
|
|
54
|
+
userId: string;
|
|
55
|
+
refreshToken: string;
|
|
56
|
+
appInstanceId: string;
|
|
57
|
+
fingerprintHash: string;
|
|
58
|
+
};
|
|
59
|
+
type AuthRefreshResponse = {
|
|
60
|
+
refreshToken: string;
|
|
61
|
+
stepUpRequired: boolean;
|
|
62
|
+
};
|
|
63
|
+
type AuthLogoutInput = {
|
|
64
|
+
userId: string;
|
|
65
|
+
refreshToken: string;
|
|
66
|
+
};
|
|
67
|
+
type PinSetInput = {
|
|
68
|
+
userId: string;
|
|
69
|
+
pin: string;
|
|
70
|
+
};
|
|
71
|
+
type PinVerifyInput = {
|
|
72
|
+
userId: string;
|
|
73
|
+
pin: string;
|
|
26
74
|
};
|
|
27
75
|
declare class FlurClient {
|
|
28
76
|
private readonly baseUrl;
|
|
@@ -37,10 +85,21 @@ declare class FlurClient {
|
|
|
37
85
|
}>;
|
|
38
86
|
onboardingStart(input: OnboardingStartInput): Promise<OnboardingStartResponse>;
|
|
39
87
|
onboardingComplete(input: OnboardingCompleteInput): Promise<OnboardingCompleteResponse>;
|
|
88
|
+
registerDevice(input: RegisterDeviceInput): Promise<RegisterDeviceResponse>;
|
|
89
|
+
authRefresh(input: AuthRefreshInput): Promise<AuthRefreshResponse>;
|
|
90
|
+
authLogout(input: AuthLogoutInput): Promise<{
|
|
91
|
+
ok: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
pinSet(input: PinSetInput): Promise<{
|
|
94
|
+
ok: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
pinVerify(input: PinVerifyInput): Promise<{
|
|
97
|
+
ok: boolean;
|
|
98
|
+
}>;
|
|
40
99
|
private requestJson;
|
|
41
100
|
}
|
|
42
101
|
|
|
43
|
-
type FlurErrorCode = "NETWORK_ERROR" | "HTTP_ERROR" | "TIMEOUT" | "UNKNOWN";
|
|
102
|
+
type FlurErrorCode = "NETWORK_ERROR" | "HTTP_ERROR" | "TIMEOUT" | "UNKNOWN" | "TOKEN_REPLAYED" | "SESSION_MISMATCH" | "PIN_INVALID" | "PIN_LOCKED" | "PIN_NOT_SET" | "STEP_UP_REQUIRED";
|
|
44
103
|
declare class FlurError extends Error {
|
|
45
104
|
readonly code: FlurErrorCode;
|
|
46
105
|
readonly status?: number;
|
|
@@ -51,4 +110,4 @@ declare class FlurError extends Error {
|
|
|
51
110
|
});
|
|
52
111
|
}
|
|
53
112
|
|
|
54
|
-
export { FlurClient, type FlurClientOptions, FlurError, type OnboardingCompleteInput, type OnboardingCompleteResponse, type OnboardingFallback, type OnboardingRiskReason, type OnboardingStartInput, type OnboardingStartResponse };
|
|
113
|
+
export { type AuthLogoutInput, type AuthRefreshInput, type AuthRefreshResponse, type DeviceTrustState, FlurClient, type FlurClientOptions, FlurError, type OnboardingCompleteInput, type OnboardingCompleteResponse, type OnboardingFallback, type OnboardingRiskReason, type OnboardingStartInput, type OnboardingStartResponse, type PinSetInput, type PinVerifyInput, type RegisterDeviceInput, type RegisterDeviceResponse };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/errors.ts
|
|
2
|
+
var backendErrorCodeSet = /* @__PURE__ */ new Set([
|
|
3
|
+
"TOKEN_REPLAYED",
|
|
4
|
+
"SESSION_MISMATCH",
|
|
5
|
+
"PIN_INVALID",
|
|
6
|
+
"PIN_LOCKED",
|
|
7
|
+
"PIN_NOT_SET",
|
|
8
|
+
"STEP_UP_REQUIRED"
|
|
9
|
+
]);
|
|
2
10
|
var FlurError = class extends Error {
|
|
3
11
|
code;
|
|
4
12
|
status;
|
|
@@ -13,12 +21,27 @@ var FlurError = class extends Error {
|
|
|
13
21
|
};
|
|
14
22
|
async function mapToFlurError(res) {
|
|
15
23
|
let details = void 0;
|
|
24
|
+
let mappedCode = "HTTP_ERROR";
|
|
25
|
+
let mappedMessage = `HTTP ${res.status}`;
|
|
16
26
|
try {
|
|
17
27
|
const ct = res.headers.get("content-type") ?? "";
|
|
18
28
|
details = ct.includes("application/json") ? await res.json() : await res.text();
|
|
29
|
+
if (details && typeof details === "object") {
|
|
30
|
+
const candidateCode = details.code;
|
|
31
|
+
const candidateMessage = details.message;
|
|
32
|
+
const fallbackMessage = details.error;
|
|
33
|
+
if (typeof candidateCode === "string" && backendErrorCodeSet.has(candidateCode)) {
|
|
34
|
+
mappedCode = candidateCode;
|
|
35
|
+
}
|
|
36
|
+
if (typeof candidateMessage === "string" && candidateMessage.length > 0) {
|
|
37
|
+
mappedMessage = candidateMessage;
|
|
38
|
+
} else if (typeof fallbackMessage === "string" && fallbackMessage.length > 0) {
|
|
39
|
+
mappedMessage = fallbackMessage;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
19
42
|
} catch {
|
|
20
43
|
}
|
|
21
|
-
return new FlurError(
|
|
44
|
+
return new FlurError(mappedMessage, mappedCode, { status: res.status, details });
|
|
22
45
|
}
|
|
23
46
|
|
|
24
47
|
// src/client.ts
|
|
@@ -51,6 +74,41 @@ var FlurClient = class {
|
|
|
51
74
|
body: JSON.stringify(input)
|
|
52
75
|
});
|
|
53
76
|
}
|
|
77
|
+
async registerDevice(input) {
|
|
78
|
+
return this.requestJson("/v1/devices/register", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "content-type": "application/json" },
|
|
81
|
+
body: JSON.stringify(input)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async authRefresh(input) {
|
|
85
|
+
return this.requestJson("/v1/auth/refresh", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "content-type": "application/json" },
|
|
88
|
+
body: JSON.stringify(input)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async authLogout(input) {
|
|
92
|
+
return this.requestJson("/v1/auth/logout", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "content-type": "application/json" },
|
|
95
|
+
body: JSON.stringify(input)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async pinSet(input) {
|
|
99
|
+
return this.requestJson("/v1/auth/pin/set", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "content-type": "application/json" },
|
|
102
|
+
body: JSON.stringify(input)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async pinVerify(input) {
|
|
106
|
+
return this.requestJson("/v1/auth/pin/verify", {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "content-type": "application/json" },
|
|
109
|
+
body: JSON.stringify(input)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
54
112
|
async requestJson(path, init) {
|
|
55
113
|
const url = `${this.baseUrl}${path}`;
|
|
56
114
|
const controller = new AbortController();
|