@softr/sdk 0.0.24 → 0.0.25
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/dist/assertions.js +1 -7
- package/dist/auth/methods/authenticator.d.ts +80 -0
- package/dist/auth/methods/authenticator.js +175 -0
- package/dist/auth/methods/code.d.ts +40 -0
- package/dist/auth/methods/code.js +74 -0
- package/dist/auth/methods/magicLink.d.ts +24 -0
- package/dist/auth/methods/magicLink.js +53 -0
- package/dist/auth/methods/password.d.ts +19 -5
- package/dist/auth/methods/password.js +36 -15
- package/dist/auth/methods/signup.d.ts +22 -14
- package/dist/auth/methods/signup.js +48 -54
- package/dist/auth/methods/social.d.ts +35 -0
- package/dist/auth/methods/social.js +58 -0
- package/dist/auth/methods/utils.d.ts +1 -1
- package/dist/auth/platform.js +185 -7
- package/dist/auth/server.d.ts +15 -0
- package/dist/auth/server.js +16 -1
- package/dist/auth/session.js +35 -2
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +2 -2
- package/skills/softr-sdk/auth/SKILL.md +222 -36
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/data-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/reading-records/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
2
|
import { writeCookies } from "../../cookies.js";
|
|
3
3
|
import { sessionConfig } from "../config.js";
|
|
4
4
|
import { makePlatform } from "../platform.js";
|
|
5
|
-
import { makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
5
|
+
import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
6
6
|
import { authError } from "./utils.js";
|
|
7
|
-
import { signIn } from "./password.js";
|
|
8
7
|
|
|
9
8
|
//#region src/auth/methods/signup.ts
|
|
10
9
|
/**
|
|
@@ -13,11 +12,13 @@ import { signIn } from "./password.js";
|
|
|
13
12
|
* User only exists once `confirmSignUp` returns the code.
|
|
14
13
|
*/
|
|
15
14
|
async function signUp(details) {
|
|
16
|
-
const result = await makePlatform(sessionConfig().applicationId).
|
|
15
|
+
const result = await makePlatform(sessionConfig().applicationId).sendSignUpCode(details);
|
|
17
16
|
if (!result.ok) {
|
|
18
|
-
if (result.status >= 500) panic(result.message);
|
|
19
17
|
if (result.status === 409) return authError("emailTaken", result.message);
|
|
20
|
-
return authError("
|
|
18
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
19
|
+
if (result.status === 400) return authError("invalidDetails", result.message);
|
|
20
|
+
if (result.status >= 500) panic(`Expected the sign-up code to be emailed, but got ${result.status}: ` + result.message);
|
|
21
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
21
22
|
}
|
|
22
23
|
return {
|
|
23
24
|
success: true,
|
|
@@ -25,63 +26,56 @@ async function signUp(details) {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
* Finishes sign-up
|
|
29
|
+
* Finishes a sign-up with the emailed code, creating the account and signing
|
|
30
|
+
* the visitor in.
|
|
29
31
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* `password` is what the account is created with. Leave it out only for an app
|
|
33
|
+
* that signs people in with an emailed code. An app expecting a password
|
|
34
|
+
* rejects a sign-up without one, and the other way round.
|
|
35
|
+
*
|
|
36
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
37
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
38
|
+
* to send them to next.
|
|
34
39
|
*/
|
|
35
40
|
async function confirmSignUp(details) {
|
|
36
41
|
const platform = makePlatform(sessionConfig().applicationId);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const { password,...rest } = details;
|
|
43
|
+
const result = password === void 0 ? await platform.signUpWithCode(rest) : await platform.signUpWithPassword({
|
|
44
|
+
...rest,
|
|
45
|
+
password
|
|
40
46
|
});
|
|
41
|
-
if (!
|
|
42
|
-
if (
|
|
43
|
-
if (
|
|
44
|
-
return authError("invalidCode",
|
|
47
|
+
if (!result.ok) {
|
|
48
|
+
if (result.status === 409) return authError("emailTaken", result.message);
|
|
49
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
50
|
+
if (result.status === 400 || result.status === 401) return authError("invalidCode", result.message);
|
|
51
|
+
if (result.status >= 500) panic(`Expected sign-up to succeed, but got ${result.status}: ` + result.message);
|
|
52
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const signedIn = await signIn({
|
|
58
|
-
email: details.email,
|
|
59
|
-
password: details.password
|
|
60
|
-
});
|
|
61
|
-
if (!signedIn.success) return authError("invalidCode", created.message);
|
|
62
|
-
return signedIn;
|
|
63
|
-
}
|
|
54
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
55
|
+
const { mfa } = result.data;
|
|
56
|
+
assert(mfa, "Expected a challenge from sign-up, but got none");
|
|
57
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
data: {
|
|
61
|
+
status: "authenticatorRequired",
|
|
62
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
const { session } = result.data;
|
|
67
|
+
assert(session, "Expected a session from sign-up, but got none");
|
|
68
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
69
|
+
user: session.user,
|
|
70
|
+
permissions: session.permissions
|
|
71
|
+
}, session.jwt)]);
|
|
72
|
+
clearPendingAuth();
|
|
71
73
|
return {
|
|
72
74
|
success: true,
|
|
73
|
-
data:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* `name` is dropped when absent rather than sent as `undefined`: the platform's
|
|
78
|
-
* validator runs on whatever key is present, and an explicit null is not the
|
|
79
|
-
* same request as no name at all.
|
|
80
|
-
*/
|
|
81
|
-
function signUpBody(details) {
|
|
82
|
-
return details.name === void 0 ? { email: details.email } : {
|
|
83
|
-
email: details.email,
|
|
84
|
-
name: details.name
|
|
75
|
+
data: {
|
|
76
|
+
status: "signedIn",
|
|
77
|
+
user: session.user
|
|
78
|
+
}
|
|
85
79
|
};
|
|
86
80
|
}
|
|
87
81
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { User } from "../user.js";
|
|
2
|
+
import { AuthError, Result } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
//#region src/auth/methods/social.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signs a visitor in with a Google token the browser obtained, creating their
|
|
8
|
+
* account the first time.
|
|
9
|
+
*
|
|
10
|
+
* Pass the id token Google Identity Services handed the page. An access token
|
|
11
|
+
* is rejected, and so is a token minted for another app. `fullName` names a
|
|
12
|
+
* first-time visitor and does nothing for one who already has an account.
|
|
13
|
+
*
|
|
14
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
15
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
16
|
+
* to send them to next.
|
|
17
|
+
*/
|
|
18
|
+
declare function signInWithGoogle(credentials: {
|
|
19
|
+
email: string;
|
|
20
|
+
token: string;
|
|
21
|
+
fullName?: string;
|
|
22
|
+
}): Promise<Result<{
|
|
23
|
+
status: "signedIn";
|
|
24
|
+
user: User;
|
|
25
|
+
} | {
|
|
26
|
+
status: "authenticatorRequired";
|
|
27
|
+
/**
|
|
28
|
+
* `verify`: they have an authenticator app and owe a code.
|
|
29
|
+
* `setup`: the app requires one and they have none yet.
|
|
30
|
+
* `setupOptional`: same, except they may skip it instead.
|
|
31
|
+
*/
|
|
32
|
+
step: "verify" | "setup" | "setupOptional";
|
|
33
|
+
}, AuthError>>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { signInWithGoogle };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
|
+
import { writeCookies } from "../../cookies.js";
|
|
3
|
+
import { sessionConfig } from "../config.js";
|
|
4
|
+
import { makePlatform } from "../platform.js";
|
|
5
|
+
import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
6
|
+
import { authError } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
//#region src/auth/methods/social.ts
|
|
9
|
+
/**
|
|
10
|
+
* Signs a visitor in with a Google token the browser obtained, creating their
|
|
11
|
+
* account the first time.
|
|
12
|
+
*
|
|
13
|
+
* Pass the id token Google Identity Services handed the page. An access token
|
|
14
|
+
* is rejected, and so is a token minted for another app. `fullName` names a
|
|
15
|
+
* first-time visitor and does nothing for one who already has an account.
|
|
16
|
+
*
|
|
17
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
18
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
19
|
+
* to send them to next.
|
|
20
|
+
*/
|
|
21
|
+
async function signInWithGoogle(credentials) {
|
|
22
|
+
const result = await makePlatform(sessionConfig().applicationId).signInWithGoogle(credentials);
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
25
|
+
if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
|
|
26
|
+
if (result.status >= 500) panic(`Expected Google sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
27
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows Google sign-in.`);
|
|
28
|
+
}
|
|
29
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
30
|
+
const { mfa } = result.data;
|
|
31
|
+
assert(mfa, "Expected a challenge from Google sign-in, but got none");
|
|
32
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: {
|
|
36
|
+
status: "authenticatorRequired",
|
|
37
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const { session } = result.data;
|
|
42
|
+
assert(session, "Expected a session from Google sign-in, but got none");
|
|
43
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
44
|
+
user: session.user,
|
|
45
|
+
permissions: session.permissions
|
|
46
|
+
}, session.jwt)]);
|
|
47
|
+
clearPendingAuth();
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
data: {
|
|
51
|
+
status: "signedIn",
|
|
52
|
+
user: session.user
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { signInWithGoogle };
|
|
@@ -10,6 +10,6 @@ type AuthError = {
|
|
|
10
10
|
code: AuthErrorCode;
|
|
11
11
|
message: string;
|
|
12
12
|
};
|
|
13
|
-
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode";
|
|
13
|
+
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated";
|
|
14
14
|
//#endregion
|
|
15
15
|
export { AuthError, Result };
|
package/dist/auth/platform.js
CHANGED
|
@@ -22,18 +22,196 @@ function makePlatform(applicationId) {
|
|
|
22
22
|
method: "POST",
|
|
23
23
|
json: credentials
|
|
24
24
|
}, z.object({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
26
|
+
session: z.object({
|
|
27
|
+
jwt: z.string(),
|
|
28
|
+
user: userSchema,
|
|
29
|
+
permissions: permissionsSchema
|
|
30
|
+
}).nullish(),
|
|
31
|
+
mfa: z.object({
|
|
32
|
+
action: z.enum([
|
|
33
|
+
"VERIFICATION_REQUIRED",
|
|
34
|
+
"SETUP_REQUIRED",
|
|
35
|
+
"SETUP_OPTIONAL"
|
|
36
|
+
]),
|
|
37
|
+
challengeToken: z.string()
|
|
38
|
+
}).nullish()
|
|
39
|
+
})),
|
|
40
|
+
sendSignInCode: (email) => request(`/v1/vibe/applications/${applicationId}/users/tokens/send-code`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
json: { email }
|
|
43
|
+
}, z.null()),
|
|
44
|
+
signInWithCode: (credentials) => request(`/v1/vibe/applications/${applicationId}/users/tokens/with-code`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
json: credentials
|
|
47
|
+
}, z.object({
|
|
48
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
49
|
+
session: z.object({
|
|
50
|
+
jwt: z.string(),
|
|
51
|
+
user: userSchema,
|
|
52
|
+
permissions: permissionsSchema
|
|
53
|
+
}).nullish(),
|
|
54
|
+
mfa: z.object({
|
|
55
|
+
action: z.enum([
|
|
56
|
+
"VERIFICATION_REQUIRED",
|
|
57
|
+
"SETUP_REQUIRED",
|
|
58
|
+
"SETUP_OPTIONAL"
|
|
59
|
+
]),
|
|
60
|
+
challengeToken: z.string()
|
|
61
|
+
}).nullish()
|
|
62
|
+
})),
|
|
63
|
+
signInWithGoogle: (body) => request(`/v1/vibe/applications/${applicationId}/users/tokens/with-google`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
json: body
|
|
66
|
+
}, z.object({
|
|
67
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
68
|
+
session: z.object({
|
|
69
|
+
jwt: z.string(),
|
|
70
|
+
user: userSchema,
|
|
71
|
+
permissions: permissionsSchema
|
|
72
|
+
}).nullish(),
|
|
73
|
+
mfa: z.object({
|
|
74
|
+
action: z.enum([
|
|
75
|
+
"VERIFICATION_REQUIRED",
|
|
76
|
+
"SETUP_REQUIRED",
|
|
77
|
+
"SETUP_OPTIONAL"
|
|
78
|
+
]),
|
|
79
|
+
challengeToken: z.string()
|
|
80
|
+
}).nullish()
|
|
28
81
|
})),
|
|
29
|
-
|
|
82
|
+
signInWithMagicLink: (body) => request(`/v1/vibe/applications/${applicationId}/users/tokens/with-magic-link`, {
|
|
30
83
|
method: "POST",
|
|
31
84
|
json: body
|
|
32
|
-
}, z.object({
|
|
33
|
-
|
|
85
|
+
}, z.object({
|
|
86
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
87
|
+
session: z.object({
|
|
88
|
+
jwt: z.string(),
|
|
89
|
+
user: userSchema,
|
|
90
|
+
permissions: permissionsSchema
|
|
91
|
+
}).nullish(),
|
|
92
|
+
mfa: z.object({
|
|
93
|
+
action: z.enum([
|
|
94
|
+
"VERIFICATION_REQUIRED",
|
|
95
|
+
"SETUP_REQUIRED",
|
|
96
|
+
"SETUP_OPTIONAL"
|
|
97
|
+
]),
|
|
98
|
+
challengeToken: z.string()
|
|
99
|
+
}).nullish()
|
|
100
|
+
})),
|
|
101
|
+
sendSignUpCode: (body) => request(`/v1/vibe/applications/${applicationId}/users/sign-up/send-code`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
json: body
|
|
104
|
+
}, z.null()),
|
|
105
|
+
signUpWithCode: (body) => request(`/v1/vibe/applications/${applicationId}/users/sign-up/with-code`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
json: body
|
|
108
|
+
}, z.object({
|
|
109
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
110
|
+
session: z.object({
|
|
111
|
+
jwt: z.string(),
|
|
112
|
+
user: userSchema,
|
|
113
|
+
permissions: permissionsSchema
|
|
114
|
+
}).nullish(),
|
|
115
|
+
mfa: z.object({
|
|
116
|
+
action: z.enum([
|
|
117
|
+
"VERIFICATION_REQUIRED",
|
|
118
|
+
"SETUP_REQUIRED",
|
|
119
|
+
"SETUP_OPTIONAL"
|
|
120
|
+
]),
|
|
121
|
+
challengeToken: z.string()
|
|
122
|
+
}).nullish()
|
|
123
|
+
})),
|
|
124
|
+
signUpWithPassword: (body) => request(`/v1/vibe/applications/${applicationId}/users/sign-up/with-password`, {
|
|
34
125
|
method: "POST",
|
|
35
126
|
json: body
|
|
36
|
-
}, z.object({
|
|
127
|
+
}, z.object({
|
|
128
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
129
|
+
session: z.object({
|
|
130
|
+
jwt: z.string(),
|
|
131
|
+
user: userSchema,
|
|
132
|
+
permissions: permissionsSchema
|
|
133
|
+
}).nullish(),
|
|
134
|
+
mfa: z.object({
|
|
135
|
+
action: z.enum([
|
|
136
|
+
"VERIFICATION_REQUIRED",
|
|
137
|
+
"SETUP_REQUIRED",
|
|
138
|
+
"SETUP_OPTIONAL"
|
|
139
|
+
]),
|
|
140
|
+
challengeToken: z.string()
|
|
141
|
+
}).nullish()
|
|
142
|
+
})),
|
|
143
|
+
authenticatorSetupInit: (token) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/setup/init`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
token
|
|
146
|
+
}, z.object({
|
|
147
|
+
otpauthUri: z.string(),
|
|
148
|
+
qrPngBase64: z.string()
|
|
149
|
+
})),
|
|
150
|
+
authenticatorSetupComplete: (token, code) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/setup/complete`, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
token,
|
|
153
|
+
json: { code }
|
|
154
|
+
}, z.object({
|
|
155
|
+
session: z.object({
|
|
156
|
+
jwt: z.string(),
|
|
157
|
+
user: userSchema,
|
|
158
|
+
permissions: permissionsSchema
|
|
159
|
+
}),
|
|
160
|
+
backupCodes: z.array(z.string())
|
|
161
|
+
})),
|
|
162
|
+
authenticatorSetupDismiss: (token) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/setup/dismiss`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
token
|
|
165
|
+
}, z.object({
|
|
166
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
167
|
+
session: z.object({
|
|
168
|
+
jwt: z.string(),
|
|
169
|
+
user: userSchema,
|
|
170
|
+
permissions: permissionsSchema
|
|
171
|
+
}).nullish(),
|
|
172
|
+
mfa: z.object({
|
|
173
|
+
action: z.enum([
|
|
174
|
+
"VERIFICATION_REQUIRED",
|
|
175
|
+
"SETUP_REQUIRED",
|
|
176
|
+
"SETUP_OPTIONAL"
|
|
177
|
+
]),
|
|
178
|
+
challengeToken: z.string()
|
|
179
|
+
}).nullish()
|
|
180
|
+
})),
|
|
181
|
+
authenticatorVerify: (token, body) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/totp/verify`, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
token,
|
|
184
|
+
json: body
|
|
185
|
+
}, z.object({
|
|
186
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
187
|
+
session: z.object({
|
|
188
|
+
jwt: z.string(),
|
|
189
|
+
user: userSchema,
|
|
190
|
+
permissions: permissionsSchema
|
|
191
|
+
}).nullish(),
|
|
192
|
+
mfa: z.object({
|
|
193
|
+
action: z.enum([
|
|
194
|
+
"VERIFICATION_REQUIRED",
|
|
195
|
+
"SETUP_REQUIRED",
|
|
196
|
+
"SETUP_OPTIONAL"
|
|
197
|
+
]),
|
|
198
|
+
challengeToken: z.string()
|
|
199
|
+
}).nullish()
|
|
200
|
+
})),
|
|
201
|
+
authenticatorSendResetCode: (token) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/reset/send-code`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
token
|
|
204
|
+
}, z.null()),
|
|
205
|
+
authenticatorReset: (token, code) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/reset`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
token,
|
|
208
|
+
json: { code }
|
|
209
|
+
}, z.null()),
|
|
210
|
+
authenticatorRemove: (token, code) => request(`/v1/vibe/applications/${applicationId}/users/authenticator/remove`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
token,
|
|
213
|
+
json: { code }
|
|
214
|
+
}, z.null()),
|
|
37
215
|
signOut: (token) => request(`/v1/applications/${applicationId}/users/sign-out`, {
|
|
38
216
|
method: "POST",
|
|
39
217
|
token
|
package/dist/auth/server.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { confirmAuthenticatorSetup, removeAuthenticator, resetAuthenticator, sendAuthenticatorResetCode, skipAuthenticatorSetup, startAuthenticatorSetup, verifyAuthenticator } from "./methods/authenticator.js";
|
|
2
|
+
import { sendSignInCode, signInWithCode } from "./methods/code.js";
|
|
3
|
+
import { signInWithMagicLink } from "./methods/magicLink.js";
|
|
1
4
|
import { signIn, signOut } from "./methods/password.js";
|
|
2
5
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
6
|
+
import { signInWithGoogle } from "./methods/social.js";
|
|
3
7
|
import "@tanstack/react-start/server-only";
|
|
4
8
|
|
|
5
9
|
//#region src/auth/server.d.ts
|
|
@@ -12,6 +16,17 @@ declare const auth: {
|
|
|
12
16
|
signOut: typeof signOut;
|
|
13
17
|
signUp: typeof signUp;
|
|
14
18
|
confirmSignUp: typeof confirmSignUp;
|
|
19
|
+
sendSignInCode: typeof sendSignInCode;
|
|
20
|
+
signInWithCode: typeof signInWithCode;
|
|
21
|
+
signInWithGoogle: typeof signInWithGoogle;
|
|
22
|
+
signInWithMagicLink: typeof signInWithMagicLink;
|
|
23
|
+
startAuthenticatorSetup: typeof startAuthenticatorSetup;
|
|
24
|
+
confirmAuthenticatorSetup: typeof confirmAuthenticatorSetup;
|
|
25
|
+
skipAuthenticatorSetup: typeof skipAuthenticatorSetup;
|
|
26
|
+
verifyAuthenticator: typeof verifyAuthenticator;
|
|
27
|
+
sendAuthenticatorResetCode: typeof sendAuthenticatorResetCode;
|
|
28
|
+
resetAuthenticator: typeof resetAuthenticator;
|
|
29
|
+
removeAuthenticator: typeof removeAuthenticator;
|
|
15
30
|
};
|
|
16
31
|
//#endregion
|
|
17
32
|
export { auth };
|
package/dist/auth/server.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { confirmAuthenticatorSetup, removeAuthenticator, resetAuthenticator, sendAuthenticatorResetCode, skipAuthenticatorSetup, startAuthenticatorSetup, verifyAuthenticator } from "./methods/authenticator.js";
|
|
2
|
+
import { sendSignInCode, signInWithCode } from "./methods/code.js";
|
|
3
|
+
import { signInWithMagicLink } from "./methods/magicLink.js";
|
|
1
4
|
import { signIn, signOut } from "./methods/password.js";
|
|
2
5
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
6
|
+
import { signInWithGoogle } from "./methods/social.js";
|
|
3
7
|
import "@tanstack/react-start/server-only";
|
|
4
8
|
|
|
5
9
|
//#region src/auth/server.ts
|
|
@@ -10,7 +14,18 @@ const auth = {
|
|
|
10
14
|
signIn,
|
|
11
15
|
signOut,
|
|
12
16
|
signUp,
|
|
13
|
-
confirmSignUp
|
|
17
|
+
confirmSignUp,
|
|
18
|
+
sendSignInCode,
|
|
19
|
+
signInWithCode,
|
|
20
|
+
signInWithGoogle,
|
|
21
|
+
signInWithMagicLink,
|
|
22
|
+
startAuthenticatorSetup,
|
|
23
|
+
confirmAuthenticatorSetup,
|
|
24
|
+
skipAuthenticatorSetup,
|
|
25
|
+
verifyAuthenticator,
|
|
26
|
+
sendAuthenticatorResetCode,
|
|
27
|
+
resetAuthenticator,
|
|
28
|
+
removeAuthenticator
|
|
14
29
|
};
|
|
15
30
|
|
|
16
31
|
//#endregion
|
package/dist/auth/session.js
CHANGED
|
@@ -9,8 +9,14 @@ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:
|
|
|
9
9
|
//#region src/auth/session.ts
|
|
10
10
|
const SESSION_TOKEN = "session_token";
|
|
11
11
|
const SESSION_DATA = "session_data";
|
|
12
|
+
const PENDING_AUTH = "pending_auth";
|
|
12
13
|
const SESSION_DATA_TTL_SECONDS = 300;
|
|
13
14
|
/**
|
|
15
|
+
* How long a visitor has to answer a second factor. The platform expires the
|
|
16
|
+
* challenge token itself. This only decides how long the browser carries it.
|
|
17
|
+
*/
|
|
18
|
+
const PENDING_AUTH_TTL_SECONDS = 600;
|
|
19
|
+
/**
|
|
14
20
|
* A browser refuses a `__Host-` cookie unless it is `Secure`, `Path=/` and free
|
|
15
21
|
* of `Domain` — that last clause is the one worth having, since it stops a
|
|
16
22
|
* sibling host planting a cookie of this name for us to honour.
|
|
@@ -67,10 +73,37 @@ function makeSessionDataCookie(session, sessionToken) {
|
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
76
|
+
* The challenge token from a sign-in that stopped at a second factor, or
|
|
77
|
+
* `null`.
|
|
78
|
+
*
|
|
79
|
+
* It is a bearer credential scoped to the authenticator endpoints, so it stays
|
|
80
|
+
* sealed in the jar the way a session token does, instead of passing through
|
|
81
|
+
* the app.
|
|
82
|
+
*/
|
|
83
|
+
function readPendingAuthToken() {
|
|
84
|
+
const { name } = sessionCookie(PENDING_AUTH);
|
|
85
|
+
return openEnvelope(readRequestCookies()[name], PENDING_AUTH);
|
|
86
|
+
}
|
|
87
|
+
function makePendingAuthCookie(token) {
|
|
88
|
+
const { name, secure } = sessionCookie(PENDING_AUTH);
|
|
89
|
+
return serializeCookie(name, sealEnvelope(token, PENDING_AUTH), {
|
|
90
|
+
maxAge: PENDING_AUTH_TTL_SECONDS,
|
|
91
|
+
secure
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Drops an outstanding challenge without touching the session. */
|
|
95
|
+
function clearPendingAuth() {
|
|
96
|
+
clearCookies([PENDING_AUTH]);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
70
99
|
* Writes rather than returns: a forgotten list would leave the session live.
|
|
71
100
|
*/
|
|
72
101
|
function clearSession() {
|
|
73
|
-
clearCookies([
|
|
102
|
+
clearCookies([
|
|
103
|
+
SESSION_TOKEN,
|
|
104
|
+
SESSION_DATA,
|
|
105
|
+
PENDING_AUTH
|
|
106
|
+
]);
|
|
74
107
|
}
|
|
75
108
|
const sessionDataSchema = z.object({
|
|
76
109
|
user: userSchema.nullable(),
|
|
@@ -163,4 +196,4 @@ function openEnvelope(raw, purpose) {
|
|
|
163
196
|
}
|
|
164
197
|
|
|
165
198
|
//#endregion
|
|
166
|
-
export { clearSession, makeSessionDataCookie, makeSessionTokenCookie, readSessionData, readSessionToken };
|
|
199
|
+
export { clearPendingAuth, clearSession, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readPendingAuthToken, readSessionData, readSessionToken };
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
14
|
+
library_version: 0.0.25
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Softr SDK core
|
|
@@ -124,4 +124,4 @@ type User = {
|
|
|
124
124
|
|
|
125
125
|
## Version
|
|
126
126
|
|
|
127
|
-
Targets `@softr/sdk` v0.0.
|
|
127
|
+
Targets `@softr/sdk` v0.0.25.
|