@softr/sdk 0.0.23 → 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 +39 -20
- package/dist/core/datasource.d.ts +3 -3
- package/dist/core/datasource.js +17 -17
- package/dist/core/integration.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/types.d.ts +3 -3
- package/dist/vite.d.ts +2 -9
- package/dist/vite.js +3 -68
- package/package.json +1 -6
- package/skills/softr-sdk/SKILL.md +3 -3
- 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 +2 -2
- package/skills/softr-sdk/reading-records/SKILL.md +3 -3
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +5 -5
- package/dist/preview-identity/preview-identity.js +0 -121
- package/dist/routes/discover.d.ts +0 -14
- package/dist/routes/discover.js +0 -305
- package/dist/routes/manifest.d.ts +0 -24
- package/dist/routes/manifest.js +0 -168
- package/dist/routes/mem-fs.js +0 -71
|
@@ -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.
|
|
@@ -46,8 +52,9 @@ function readSessionData(sessionToken) {
|
|
|
46
52
|
permissions: payload.permissions
|
|
47
53
|
};
|
|
48
54
|
}
|
|
49
|
-
function makeSessionTokenCookie(token
|
|
50
|
-
|
|
55
|
+
function makeSessionTokenCookie(token) {
|
|
56
|
+
const { name, secure } = sessionCookie(SESSION_TOKEN);
|
|
57
|
+
return serializeCookie(name, sealEnvelope(token, SESSION_TOKEN), {
|
|
51
58
|
maxAge: sessionConfig().sessionMaxAge,
|
|
52
59
|
secure
|
|
53
60
|
});
|
|
@@ -66,23 +73,37 @@ function makeSessionDataCookie(session, sessionToken) {
|
|
|
66
73
|
});
|
|
67
74
|
}
|
|
68
75
|
/**
|
|
69
|
-
*
|
|
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.
|
|
70
82
|
*/
|
|
71
|
-
function
|
|
72
|
-
|
|
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
|
+
});
|
|
73
93
|
}
|
|
74
|
-
/**
|
|
75
|
-
function
|
|
76
|
-
|
|
94
|
+
/** Drops an outstanding challenge without touching the session. */
|
|
95
|
+
function clearPendingAuth() {
|
|
96
|
+
clearCookies([PENDING_AUTH]);
|
|
77
97
|
}
|
|
78
98
|
/**
|
|
79
|
-
*
|
|
80
|
-
* scheme, for a caller that only needs to know whether one is present and cannot
|
|
81
|
-
* decode it. Same `secure` argument as `makeSessionTokenCookie`, so a caller that
|
|
82
|
-
* writes and a caller that probes cannot disagree about the name.
|
|
99
|
+
* Writes rather than returns: a forgotten list would leave the session live.
|
|
83
100
|
*/
|
|
84
|
-
function
|
|
85
|
-
|
|
101
|
+
function clearSession() {
|
|
102
|
+
clearCookies([
|
|
103
|
+
SESSION_TOKEN,
|
|
104
|
+
SESSION_DATA,
|
|
105
|
+
PENDING_AUTH
|
|
106
|
+
]);
|
|
86
107
|
}
|
|
87
108
|
const sessionDataSchema = z.object({
|
|
88
109
|
user: userSchema.nullable(),
|
|
@@ -95,13 +116,11 @@ const sessionDataSchema = z.object({
|
|
|
95
116
|
* schemes, and the one left behind would revive if the scheme changed back.
|
|
96
117
|
*/
|
|
97
118
|
function clearCookies(bases) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
function serializeClearingCookies(bases, secure) {
|
|
101
|
-
return bases.flatMap((base) => secure ? [sessionCookieName(base, secure), base] : [base]).map((name) => serializeCookie(name, "", {
|
|
119
|
+
const secure = requestIsSecure();
|
|
120
|
+
writeCookies(bases.flatMap((base) => secure ? [sessionCookieName(base, secure), base] : [base]).map((name) => serializeCookie(name, "", {
|
|
102
121
|
maxAge: 0,
|
|
103
122
|
secure
|
|
104
|
-
}));
|
|
123
|
+
})));
|
|
105
124
|
}
|
|
106
125
|
/**
|
|
107
126
|
* Over the plaintext token, never the cookie value: that is resealed with a
|
|
@@ -177,4 +196,4 @@ function openEnvelope(raw, purpose) {
|
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
//#endregion
|
|
180
|
-
export { clearSession,
|
|
199
|
+
export { clearPendingAuth, clearSession, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readPendingAuthToken, readSessionData, readSessionToken };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChartOptions, ChartResult, CreateOptions, GetOptions, ListOptions, ListResult, MetricOptions, MetricResult, RecordFieldsFor, SoftrRecord, UpdateOptions } from "../types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/core/datasource.d.ts
|
|
4
4
|
declare function listRecords<Id extends string, Sel extends Record<string, string> = Record<never, never>>(datasourceId: Id, options?: Omit<ListOptions, 'select'> & {
|
|
@@ -17,7 +17,7 @@ declare function updateRecord<Id extends string, Sel extends Record<string, stri
|
|
|
17
17
|
}): Promise<SoftrRecord<RecordFieldsFor<Id, Sel>>>;
|
|
18
18
|
declare function deleteRecord(datasourceId: string, id: string): Promise<void>;
|
|
19
19
|
declare function getMetric(datasourceId: string, options: MetricOptions): Promise<MetricResult>;
|
|
20
|
-
declare function
|
|
20
|
+
declare function getChart(datasourceId: string, options: ChartOptions): Promise<ChartResult>;
|
|
21
21
|
declare const ds: {
|
|
22
22
|
list: typeof listRecords;
|
|
23
23
|
get: typeof getRecord;
|
|
@@ -25,7 +25,7 @@ declare const ds: {
|
|
|
25
25
|
update: typeof updateRecord;
|
|
26
26
|
delete: typeof deleteRecord;
|
|
27
27
|
metric: typeof getMetric;
|
|
28
|
-
|
|
28
|
+
chart: typeof getChart;
|
|
29
29
|
};
|
|
30
30
|
//#endregion
|
|
31
31
|
export { ds };
|