@softr/sdk 0.0.63-alpha.0 → 0.0.64-alpha.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/dist/action.d.ts +1 -31
- package/dist/action.js +1 -26
- package/dist/auth/isomorphic.d.ts +60 -1
- package/dist/auth/isomorphic.js +63 -5
- package/dist/auth/methods/passwordLink.d.ts +47 -0
- package/dist/auth/methods/passwordLink.js +86 -0
- package/dist/auth/methods/settings.d.ts +11 -0
- package/dist/auth/methods/settings.js +17 -0
- package/dist/auth/methods/signup.js +1 -0
- package/dist/auth/methods/social.js +1 -0
- package/dist/auth/methods/utils.d.ts +1 -1
- package/dist/auth/platform.d.ts +46 -0
- package/dist/auth/platform.js +54 -0
- package/dist/auth/server.d.ts +6 -0
- package/dist/auth/server.js +7 -1
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/core/file-upload.d.ts +2 -7
- package/dist/core/file-upload.js +1 -6
- package/dist/core/use-file-upload.d.ts +2 -2
- package/dist/datasource/record.js +11 -5
- package/dist/default-error-overlay.d.ts +2 -2
- package/dist/formatting.d.ts +3 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/mcp.d.ts +2 -8
- package/dist/mcp.js +8 -8
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +2 -2
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -2
- package/dist/softr-config.d.ts +1 -0
- package/dist/softr-config.js +26 -5
- package/dist/visibility/route.d.ts +26 -6
- package/dist/visibility/route.js +36 -13
- package/package.json +33 -12
- package/schema/softr.schema.json +42 -1
- package/skills/softr-sdk/SKILL.md +7 -10
- package/skills/softr-sdk/auth/SKILL.md +129 -31
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/file-uploads/SKILL.md +4 -5
- package/skills/softr-sdk/formatting/SKILL.md +4 -4
- package/skills/softr-sdk/integrations/SKILL.md +1 -1
- package/skills/softr-sdk/mcp/SKILL.md +7 -14
- package/skills/softr-sdk/pwa/SKILL.md +3 -3
- package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/record-queries/SKILL.md +1 -1
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +24 -12
- package/skills/softr-sdk/workflows/SKILL.md +1 -1
- package/dist/comments/comments.d.ts +0 -168
- package/dist/comments/comments.js +0 -413
- package/dist/comments/use-comment-composer.d.ts +0 -46
- package/dist/comments/use-comment-composer.js +0 -134
- package/skills/softr-sdk/comments/SKILL.md +0 -181
package/dist/action.d.ts
CHANGED
|
@@ -234,35 +234,5 @@ type IntegrationDefinition<TId extends string> = {
|
|
|
234
234
|
* const res = await listEvents({ query: { timeMin: now, singleEvents: "true" } })
|
|
235
235
|
*/
|
|
236
236
|
declare function defineIntegration<const TId extends string>(definition: IntegrationDefinition<TId>): IntegrationAction<TId>;
|
|
237
|
-
type CommentsResource = {
|
|
238
|
-
type: "COMMENTS";
|
|
239
|
-
datasourceId: SoftrDatasourceId;
|
|
240
|
-
};
|
|
241
|
-
/** Comment threads on the records of one datasource. It has no `permissions`. */
|
|
242
|
-
type CommentsAction<TId extends string = string> = {
|
|
243
|
-
id: TId;
|
|
244
|
-
resource: CommentsResource;
|
|
245
|
-
};
|
|
246
|
-
type CommentsDefinition<TId extends string> = {
|
|
247
|
-
id: TId;
|
|
248
|
-
datasourceId: SoftrDatasourceId;
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* Declares comment threads on the records of a datasource. Each record has one thread.
|
|
252
|
-
*
|
|
253
|
-
* Who may read, post and delete comes from the app's comment settings in Softr, not from code. Two
|
|
254
|
-
* declarations on the same datasource share the same threads.
|
|
255
|
-
*
|
|
256
|
-
* Pass the returned object to `comments.*` from `@softr/sdk/server`.
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* const orderComments = defineComments({
|
|
260
|
-
* id: "orderComments",
|
|
261
|
-
* datasourceId: "8f14e45f-ceea-467a-9d0b-2c1a0e3b5d77",
|
|
262
|
-
* })
|
|
263
|
-
*
|
|
264
|
-
* const thread = await comments.list(orderComments, { recordId })
|
|
265
|
-
*/
|
|
266
|
-
declare function defineComments<const TId extends string>(definition: CommentsDefinition<TId>): CommentsAction<TId>;
|
|
267
237
|
//#endregion
|
|
268
|
-
export { ActionData, ActionPermissions, ActionRecord, ActionShape,
|
|
238
|
+
export { ActionData, ActionPermissions, ActionRecord, ActionShape, DatasourceAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineIntegration, defineMutation, defineQuery, defineWorkflow };
|
package/dist/action.js
CHANGED
|
@@ -50,31 +50,6 @@ function defineIntegration(definition) {
|
|
|
50
50
|
permissions
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Declares comment threads on the records of a datasource. Each record has one thread.
|
|
55
|
-
*
|
|
56
|
-
* Who may read, post and delete comes from the app's comment settings in Softr, not from code. Two
|
|
57
|
-
* declarations on the same datasource share the same threads.
|
|
58
|
-
*
|
|
59
|
-
* Pass the returned object to `comments.*` from `@softr/sdk/server`.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* const orderComments = defineComments({
|
|
63
|
-
* id: "orderComments",
|
|
64
|
-
* datasourceId: "8f14e45f-ceea-467a-9d0b-2c1a0e3b5d77",
|
|
65
|
-
* })
|
|
66
|
-
*
|
|
67
|
-
* const thread = await comments.list(orderComments, { recordId })
|
|
68
|
-
*/
|
|
69
|
-
function defineComments(definition) {
|
|
70
|
-
return {
|
|
71
|
-
id: definition.id,
|
|
72
|
-
resource: {
|
|
73
|
-
type: "COMMENTS",
|
|
74
|
-
datasourceId: definition.datasourceId
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
53
|
function isDatasourceAction(value) {
|
|
79
54
|
if (typeof value !== "object" || value === null) return false;
|
|
80
55
|
const candidate = value;
|
|
@@ -83,4 +58,4 @@ function isDatasourceAction(value) {
|
|
|
83
58
|
}
|
|
84
59
|
|
|
85
60
|
//#endregion
|
|
86
|
-
export {
|
|
61
|
+
export { defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
|
|
@@ -2,6 +2,8 @@ import { User } from "./user.js";
|
|
|
2
2
|
import { Formatting } from "../formatting.js";
|
|
3
3
|
import { Permissions } from "./permissions.js";
|
|
4
4
|
import { SoftrContext } from "./context.js";
|
|
5
|
+
import * as react0 from "react";
|
|
6
|
+
import { ReactNode } from "react";
|
|
5
7
|
import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
6
8
|
|
|
7
9
|
//#region src/auth/isomorphic.d.ts
|
|
@@ -54,5 +56,62 @@ declare function initialSoftrContext(): SoftrContext;
|
|
|
54
56
|
* }
|
|
55
57
|
*/
|
|
56
58
|
declare function useCurrentUser(): User | null;
|
|
59
|
+
/** Which ways in this app offers, each named for the `auth` call it unlocks. */
|
|
60
|
+
type AuthOptions = {
|
|
61
|
+
/** `auth.signIn` */
|
|
62
|
+
password: boolean;
|
|
63
|
+
/** `auth.sendSignInCode`, then `auth.signInWithCode` */
|
|
64
|
+
emailCode: boolean;
|
|
65
|
+
/** `auth.signInWithMagicLink` */
|
|
66
|
+
magicLink: boolean;
|
|
67
|
+
/** `auth.signInWithGoogle`, with the client id to start Google sign-in with. */
|
|
68
|
+
google: {
|
|
69
|
+
clientId: string;
|
|
70
|
+
} | null;
|
|
71
|
+
/** `auth.signUp`, then `auth.confirmSignUp` */
|
|
72
|
+
signUp: boolean;
|
|
73
|
+
/** The builder asked for strong passwords whenever a visitor sets one. */
|
|
74
|
+
strongPasswords: boolean;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Loads the app's {@link AuthOptions}. Call it from the auth layout's loader
|
|
78
|
+
* and hand the result to {@link AuthSettingsProvider}.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* export const Route = createFileRoute("/_auth")({
|
|
82
|
+
* loader: () => getAuthSettings(),
|
|
83
|
+
* staleTime: 60_000,
|
|
84
|
+
* })
|
|
85
|
+
*/
|
|
86
|
+
declare const getAuthSettings: _tanstack_react_start0.OptionalFetcher<undefined, undefined, Promise<AuthOptions>>;
|
|
87
|
+
/**
|
|
88
|
+
* Makes the auth options readable with {@link useAuthSettings} anywhere below.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* function AuthLayout() {
|
|
92
|
+
* const authSettings = Route.useLoaderData()
|
|
93
|
+
* return (
|
|
94
|
+
* <AuthSettingsProvider value={authSettings}>
|
|
95
|
+
* <Outlet />
|
|
96
|
+
* </AuthSettingsProvider>
|
|
97
|
+
* )
|
|
98
|
+
* }
|
|
99
|
+
*/
|
|
100
|
+
declare function AuthSettingsProvider({
|
|
101
|
+
value,
|
|
102
|
+
children
|
|
103
|
+
}: {
|
|
104
|
+
value: AuthOptions;
|
|
105
|
+
children?: ReactNode;
|
|
106
|
+
}): react0.JSX.Element;
|
|
107
|
+
/**
|
|
108
|
+
* Which ways in this app offers. Requires an {@link AuthSettingsProvider}
|
|
109
|
+
* above it.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* const options = useAuthSettings()
|
|
113
|
+
* return options.password && <PasswordForm />
|
|
114
|
+
*/
|
|
115
|
+
declare function useAuthSettings(): AuthOptions;
|
|
57
116
|
//#endregion
|
|
58
|
-
export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser };
|
|
117
|
+
export { AuthOptions, AuthSettingsProvider, getAuthSettings, getSoftrContext, initialSoftrContext, softrAuthMiddleware, useAuthSettings, useCurrentUser };
|
package/dist/auth/isomorphic.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { assert, panic } from "../assertions.js";
|
|
1
|
+
import { assert, panic, requireDefined } from "../assertions.js";
|
|
2
2
|
import { readUserTimezone, writeCookies } from "../cookies.js";
|
|
3
3
|
import { sessionConfig } from "./config.js";
|
|
4
4
|
import { clearSession, makeSessionDataCookie, readSessionData, readSessionToken } from "./session.js";
|
|
5
5
|
import { makePlatform } from "./platform.js";
|
|
6
6
|
import { defaultFormatting, resolveFormatting } from "../formatting.js";
|
|
7
7
|
import { readLocaleSettings } from "../locale-settings.js";
|
|
8
|
+
import { settings } from "./methods/settings.js";
|
|
8
9
|
import { bootErrorMiddleware } from "../boot-error.js";
|
|
9
10
|
import { pwaMiddleware } from "../pwa.js";
|
|
11
|
+
import { createContext, useContext } from "react";
|
|
10
12
|
import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
|
|
11
13
|
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
12
14
|
import { rootRouteId, useRouteContext } from "@tanstack/react-router";
|
|
15
|
+
import { jsx } from "react/jsx-runtime";
|
|
13
16
|
|
|
14
|
-
//#region src/auth/isomorphic.
|
|
17
|
+
//#region src/auth/isomorphic.tsx
|
|
15
18
|
/**
|
|
16
19
|
* Resolves the Softr visitor, their permissions and the app's formatting for
|
|
17
20
|
* every request, so that `getSoftrContext` and the route guards have something
|
|
@@ -41,11 +44,11 @@ function softrAuthMiddleware() {
|
|
|
41
44
|
writeCookies([makeSessionDataCookie(session, token)]);
|
|
42
45
|
return session;
|
|
43
46
|
}
|
|
44
|
-
const [{ user, permissions }, settings] = await Promise.all([getSession(), readLocaleSettings()]);
|
|
47
|
+
const [{ user, permissions }, settings$1] = await Promise.all([getSession(), readLocaleSettings()]);
|
|
45
48
|
return next({ context: {
|
|
46
49
|
user,
|
|
47
50
|
permissions,
|
|
48
|
-
formatting: resolveFormatting(settings, getRequestHeader("accept-language") ?? null, readUserTimezone())
|
|
51
|
+
formatting: resolveFormatting(settings$1, getRequestHeader("accept-language") ?? null, readUserTimezone())
|
|
49
52
|
} });
|
|
50
53
|
});
|
|
51
54
|
}
|
|
@@ -94,6 +97,61 @@ function useCurrentUser() {
|
|
|
94
97
|
select: (context) => context.user
|
|
95
98
|
});
|
|
96
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Loads the app's {@link AuthOptions}. Call it from the auth layout's loader
|
|
102
|
+
* and hand the result to {@link AuthSettingsProvider}.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* export const Route = createFileRoute("/_auth")({
|
|
106
|
+
* loader: () => getAuthSettings(),
|
|
107
|
+
* staleTime: 60_000,
|
|
108
|
+
* })
|
|
109
|
+
*/
|
|
110
|
+
const getAuthSettings = createServerFn({ method: "GET" }).handler(async () => {
|
|
111
|
+
const raw = await settings();
|
|
112
|
+
const on = (method) => raw.loginEnabled && raw.authenticationMethods.includes(method);
|
|
113
|
+
const emailMethods = on("EMAIL") ? raw.emailAuth?.loginMethods ?? [] : [];
|
|
114
|
+
const clientId = raw.googleAuth?.clientId?.trim();
|
|
115
|
+
return {
|
|
116
|
+
password: emailMethods.includes("PASSWORD"),
|
|
117
|
+
emailCode: emailMethods.includes("EMAIL_CODE"),
|
|
118
|
+
magicLink: on("MAGIC_LINK"),
|
|
119
|
+
google: on("GOOGLE") && clientId ? { clientId } : null,
|
|
120
|
+
signUp: raw.loginEnabled && (raw.signUpPolicy === "OPEN_TO_PUBLIC" || raw.signUpPolicy === "DOMAIN_RESTRICTED"),
|
|
121
|
+
strongPasswords: on("EMAIL") && (raw.emailAuth?.strongPasswords ?? false)
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
const AuthSettingsContext = createContext(null);
|
|
125
|
+
/**
|
|
126
|
+
* Makes the auth options readable with {@link useAuthSettings} anywhere below.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* function AuthLayout() {
|
|
130
|
+
* const authSettings = Route.useLoaderData()
|
|
131
|
+
* return (
|
|
132
|
+
* <AuthSettingsProvider value={authSettings}>
|
|
133
|
+
* <Outlet />
|
|
134
|
+
* </AuthSettingsProvider>
|
|
135
|
+
* )
|
|
136
|
+
* }
|
|
137
|
+
*/
|
|
138
|
+
function AuthSettingsProvider({ value, children }) {
|
|
139
|
+
return /* @__PURE__ */ jsx(AuthSettingsContext.Provider, {
|
|
140
|
+
value,
|
|
141
|
+
children
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Which ways in this app offers. Requires an {@link AuthSettingsProvider}
|
|
146
|
+
* above it.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const options = useAuthSettings()
|
|
150
|
+
* return options.password && <PasswordForm />
|
|
151
|
+
*/
|
|
152
|
+
function useAuthSettings() {
|
|
153
|
+
return requireDefined(useContext(AuthSettingsContext), "useAuthSettings needs an AuthSettingsProvider above it.");
|
|
154
|
+
}
|
|
97
155
|
|
|
98
156
|
//#endregion
|
|
99
|
-
export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser };
|
|
157
|
+
export { AuthSettingsProvider, getAuthSettings, getSoftrContext, initialSoftrContext, softrAuthMiddleware, useAuthSettings, useCurrentUser };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { User } from "../user.js";
|
|
2
|
+
import { AuthError, Result } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
//#region src/auth/methods/passwordLink.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emails a link to set a new password.
|
|
8
|
+
*
|
|
9
|
+
* Succeeds whether or not the address has an account, and sends nothing when
|
|
10
|
+
* it has none.
|
|
11
|
+
*/
|
|
12
|
+
declare function requestPasswordReset(details: {
|
|
13
|
+
email: string;
|
|
14
|
+
}): Promise<Result<null, AuthError>>;
|
|
15
|
+
/**
|
|
16
|
+
* Checks a password reset or invitation link, and returns the address it was
|
|
17
|
+
* mailed to. The link stays usable.
|
|
18
|
+
*/
|
|
19
|
+
declare function checkPasswordLink(details: {
|
|
20
|
+
token: string;
|
|
21
|
+
}): Promise<Result<{
|
|
22
|
+
email: string;
|
|
23
|
+
}, AuthError>>;
|
|
24
|
+
/**
|
|
25
|
+
* Sets a new password with a password reset or invitation link, and signs the
|
|
26
|
+
* visitor in.
|
|
27
|
+
*
|
|
28
|
+
* Any success uses up the link, `authenticatorRequired` included, and ends
|
|
29
|
+
* the visitor's other sessions.
|
|
30
|
+
*/
|
|
31
|
+
declare function setPasswordWithLink(details: {
|
|
32
|
+
token: string;
|
|
33
|
+
password: string;
|
|
34
|
+
}): Promise<Result<{
|
|
35
|
+
status: "signedIn";
|
|
36
|
+
user: User;
|
|
37
|
+
} | {
|
|
38
|
+
status: "authenticatorRequired";
|
|
39
|
+
/**
|
|
40
|
+
* `verify`: they have an authenticator app and owe a code.
|
|
41
|
+
* `setup`: the app requires one and they have none yet.
|
|
42
|
+
* `setupOptional`: same, except they may skip it instead.
|
|
43
|
+
*/
|
|
44
|
+
step: "verify" | "setup" | "setupOptional";
|
|
45
|
+
}, AuthError>>;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { checkPasswordLink, requestPasswordReset, setPasswordWithLink };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
|
+
import { writeCookies } from "../../cookies.js";
|
|
3
|
+
import { sessionConfig } from "../config.js";
|
|
4
|
+
import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
5
|
+
import { makePlatform } from "../platform.js";
|
|
6
|
+
import { authError } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
//#region src/auth/methods/passwordLink.ts
|
|
9
|
+
/**
|
|
10
|
+
* Emails a link to set a new password.
|
|
11
|
+
*
|
|
12
|
+
* Succeeds whether or not the address has an account, and sends nothing when
|
|
13
|
+
* it has none.
|
|
14
|
+
*/
|
|
15
|
+
async function requestPasswordReset(details) {
|
|
16
|
+
const result = await makePlatform(sessionConfig().applicationId).forgotPassword(details);
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
19
|
+
panic(`Expected the password reset link to be emailed, but got ${result.status}: ${result.message}.`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
success: true,
|
|
23
|
+
data: null
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks a password reset or invitation link, and returns the address it was
|
|
28
|
+
* mailed to. The link stays usable.
|
|
29
|
+
*/
|
|
30
|
+
async function checkPasswordLink(details) {
|
|
31
|
+
const result = await makePlatform(sessionConfig().applicationId).checkPasswordToken(details);
|
|
32
|
+
if (!result.ok) {
|
|
33
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
34
|
+
if (result.status === 400 || result.status === 401) return authError("linkExpired", result.message);
|
|
35
|
+
panic(`Expected the link to be checked, but got ${result.status}: ${result.message}.`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
data: result.data
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sets a new password with a password reset or invitation link, and signs the
|
|
44
|
+
* visitor in.
|
|
45
|
+
*
|
|
46
|
+
* Any success uses up the link, `authenticatorRequired` included, and ends
|
|
47
|
+
* the visitor's other sessions.
|
|
48
|
+
*/
|
|
49
|
+
async function setPasswordWithLink(details) {
|
|
50
|
+
const result = await makePlatform(sessionConfig().applicationId).setPassword(details);
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
53
|
+
if (result.status === 400 && result.message === "Password must be at least 8 characters long.") return authError("invalidDetails", result.message);
|
|
54
|
+
if (result.status === 400 || result.status === 401) return authError("linkExpired", result.message);
|
|
55
|
+
panic(`Expected the password to be set, but got ${result.status}: ${result.message}.`);
|
|
56
|
+
}
|
|
57
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
58
|
+
const { mfa } = result.data;
|
|
59
|
+
assert(mfa, "Expected a challenge from setting a password, but got none");
|
|
60
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
data: {
|
|
64
|
+
status: "authenticatorRequired",
|
|
65
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const { session } = result.data;
|
|
70
|
+
assert(session, "Expected a session from setting a password, but got none");
|
|
71
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
72
|
+
user: session.user,
|
|
73
|
+
permissions: session.permissions
|
|
74
|
+
}, session.jwt)]);
|
|
75
|
+
clearPendingAuth();
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
data: {
|
|
79
|
+
status: "signedIn",
|
|
80
|
+
user: session.user
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { checkPasswordLink, requestPasswordReset, setPasswordWithLink };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AuthSettings } from "../platform.js";
|
|
2
|
+
|
|
3
|
+
//#region src/auth/methods/settings.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How visitors may sign in and sign up to this app, as configured in Studio.
|
|
7
|
+
* A login or sign-up page renders only the methods it lists.
|
|
8
|
+
*/
|
|
9
|
+
declare function settings(): Promise<AuthSettings>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { settings };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { panic } from "../../assertions.js";
|
|
2
|
+
import { sessionConfig } from "../config.js";
|
|
3
|
+
import { makePlatform } from "../platform.js";
|
|
4
|
+
|
|
5
|
+
//#region src/auth/methods/settings.ts
|
|
6
|
+
/**
|
|
7
|
+
* How visitors may sign in and sign up to this app, as configured in Studio.
|
|
8
|
+
* A login or sign-up page renders only the methods it lists.
|
|
9
|
+
*/
|
|
10
|
+
async function settings() {
|
|
11
|
+
const result = await makePlatform(sessionConfig().applicationId).authSettings();
|
|
12
|
+
if (!result.ok) panic(`Expected the auth settings, but got ${result.status}: ${result.message}`);
|
|
13
|
+
return result.data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { settings };
|
|
@@ -17,6 +17,7 @@ async function signUp(details) {
|
|
|
17
17
|
if (result.status === 409) return authError("emailTaken", result.message);
|
|
18
18
|
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
19
19
|
if (result.status === 400) return authError("invalidDetails", result.message);
|
|
20
|
+
if (result.status === 401 && result.message === "Email domain not allowed") return authError("emailDomainNotAllowed", result.message);
|
|
20
21
|
if (result.status >= 500) panic(`Expected the sign-up code to be emailed, but got ${result.status}: ` + result.message);
|
|
21
22
|
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
22
23
|
}
|
|
@@ -22,6 +22,7 @@ async function signInWithGoogle(credentials) {
|
|
|
22
22
|
const result = await makePlatform(sessionConfig().applicationId).signInWithGoogle(credentials);
|
|
23
23
|
if (!result.ok) {
|
|
24
24
|
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
25
|
+
if (result.status === 400 && result.message.startsWith("Emails with ") && result.message.endsWith(" domain is not allowed to signup.")) return authError("emailDomainNotAllowed", result.message);
|
|
25
26
|
if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
|
|
26
27
|
if (result.status >= 500) panic(`Expected Google sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
27
28
|
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows Google sign-in.`);
|
|
@@ -10,6 +10,6 @@ type AuthError = {
|
|
|
10
10
|
code: AuthErrorCode;
|
|
11
11
|
message: string;
|
|
12
12
|
};
|
|
13
|
-
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated";
|
|
13
|
+
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated" | "emailDomainNotAllowed" | "linkExpired";
|
|
14
14
|
//#endregion
|
|
15
15
|
export { AuthError, Result };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import "./user.js";
|
|
2
|
+
import "./context.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/auth/platform.d.ts
|
|
6
|
+
|
|
7
|
+
declare const authSettingsSchema: z.ZodObject<{
|
|
8
|
+
loginEnabled: z.ZodBoolean;
|
|
9
|
+
authenticationMethods: z.ZodArray<z.ZodEnum<{
|
|
10
|
+
EMAIL: "EMAIL";
|
|
11
|
+
SSO: "SSO";
|
|
12
|
+
GOOGLE: "GOOGLE";
|
|
13
|
+
MAGIC_LINK: "MAGIC_LINK";
|
|
14
|
+
}>>;
|
|
15
|
+
signUpPolicy: z.ZodNullable<z.ZodEnum<{
|
|
16
|
+
DISABLED: "DISABLED";
|
|
17
|
+
OPEN_TO_PUBLIC: "OPEN_TO_PUBLIC";
|
|
18
|
+
DOMAIN_RESTRICTED: "DOMAIN_RESTRICTED";
|
|
19
|
+
}>>;
|
|
20
|
+
sessionExpirationDays: z.ZodNullable<z.ZodNumber>;
|
|
21
|
+
inactivityTimeoutMinutes: z.ZodNullable<z.ZodNumber>;
|
|
22
|
+
emailAuth: z.ZodNullable<z.ZodObject<{
|
|
23
|
+
loginMethods: z.ZodArray<z.ZodEnum<{
|
|
24
|
+
PASSWORD: "PASSWORD";
|
|
25
|
+
EMAIL_CODE: "EMAIL_CODE";
|
|
26
|
+
}>>;
|
|
27
|
+
strongPasswords: z.ZodBoolean;
|
|
28
|
+
emailCodeExpirationMinutes: z.ZodNullable<z.ZodNumber>;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
googleAuth: z.ZodNullable<z.ZodObject<{
|
|
31
|
+
clientId: z.ZodNullable<z.ZodString>;
|
|
32
|
+
userType: z.ZodNullable<z.ZodEnum<{
|
|
33
|
+
INTERNAL: "INTERNAL";
|
|
34
|
+
EXTERNAL: "EXTERNAL";
|
|
35
|
+
}>>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
sso: z.ZodNullable<z.ZodObject<{
|
|
38
|
+
type: z.ZodNullable<z.ZodEnum<{
|
|
39
|
+
SAML: "SAML";
|
|
40
|
+
OPEN_ID: "OPEN_ID";
|
|
41
|
+
}>>;
|
|
42
|
+
}, z.core.$strip>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
type AuthSettings = z.infer<typeof authSettingsSchema>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { AuthSettings };
|
package/dist/auth/platform.js
CHANGED
|
@@ -6,8 +6,35 @@ import { userSchema } from "./user.js";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
8
|
//#region src/auth/platform.ts
|
|
9
|
+
const authSettingsSchema = z.object({
|
|
10
|
+
loginEnabled: z.boolean(),
|
|
11
|
+
authenticationMethods: z.array(z.enum([
|
|
12
|
+
"EMAIL",
|
|
13
|
+
"SSO",
|
|
14
|
+
"GOOGLE",
|
|
15
|
+
"MAGIC_LINK"
|
|
16
|
+
])),
|
|
17
|
+
signUpPolicy: z.enum([
|
|
18
|
+
"DISABLED",
|
|
19
|
+
"OPEN_TO_PUBLIC",
|
|
20
|
+
"DOMAIN_RESTRICTED"
|
|
21
|
+
]).nullable(),
|
|
22
|
+
sessionExpirationDays: z.number().nullable(),
|
|
23
|
+
inactivityTimeoutMinutes: z.number().nullable(),
|
|
24
|
+
emailAuth: z.object({
|
|
25
|
+
loginMethods: z.array(z.enum(["PASSWORD", "EMAIL_CODE"])),
|
|
26
|
+
strongPasswords: z.boolean(),
|
|
27
|
+
emailCodeExpirationMinutes: z.number().nullable()
|
|
28
|
+
}).nullable(),
|
|
29
|
+
googleAuth: z.object({
|
|
30
|
+
clientId: z.string().nullable(),
|
|
31
|
+
userType: z.enum(["INTERNAL", "EXTERNAL"]).nullable()
|
|
32
|
+
}).nullable(),
|
|
33
|
+
sso: z.object({ type: z.enum(["SAML", "OPEN_ID"]).nullable() }).nullable()
|
|
34
|
+
});
|
|
9
35
|
function makePlatform(applicationId) {
|
|
10
36
|
return {
|
|
37
|
+
authSettings: () => request(`/v1/vibe/applications/${applicationId}/users/auth-settings`, { method: "GET" }, authSettingsSchema),
|
|
11
38
|
session: async (token) => {
|
|
12
39
|
const result = await request(`/v1/vibe/applications/${applicationId}/users/session`, {
|
|
13
40
|
method: "GET",
|
|
@@ -99,6 +126,33 @@ function makePlatform(applicationId) {
|
|
|
99
126
|
challengeToken: z.string()
|
|
100
127
|
}).nullish()
|
|
101
128
|
})),
|
|
129
|
+
forgotPassword: (body) => request(`/v1/vibe/applications/${applicationId}/users/forgot-password`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
json: body
|
|
132
|
+
}, z.null()),
|
|
133
|
+
checkPasswordToken: (body) => request(`/v1/vibe/applications/${applicationId}/users/set-password/check`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
json: body
|
|
136
|
+
}, z.object({ email: z.string() })),
|
|
137
|
+
setPassword: (body) => request(`/v1/vibe/applications/${applicationId}/users/set-password`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
json: body
|
|
140
|
+
}, z.object({
|
|
141
|
+
status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
|
|
142
|
+
session: z.object({
|
|
143
|
+
jwt: z.string(),
|
|
144
|
+
user: userSchema,
|
|
145
|
+
permissions: permissionsSchema
|
|
146
|
+
}).nullish(),
|
|
147
|
+
mfa: z.object({
|
|
148
|
+
action: z.enum([
|
|
149
|
+
"VERIFICATION_REQUIRED",
|
|
150
|
+
"SETUP_REQUIRED",
|
|
151
|
+
"SETUP_OPTIONAL"
|
|
152
|
+
]),
|
|
153
|
+
challengeToken: z.string()
|
|
154
|
+
}).nullish()
|
|
155
|
+
})),
|
|
102
156
|
sendSignUpCode: (body) => request(`/v1/vibe/applications/${applicationId}/users/sign-up/send-code`, {
|
|
103
157
|
method: "POST",
|
|
104
158
|
json: body
|
package/dist/auth/server.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { confirmAuthenticatorSetup, removeAuthenticator, resetAuthenticator, sen
|
|
|
2
2
|
import { sendSignInCode, signInWithCode } from "./methods/code.js";
|
|
3
3
|
import { signInWithMagicLink } from "./methods/magicLink.js";
|
|
4
4
|
import { signIn, signOut } from "./methods/password.js";
|
|
5
|
+
import { checkPasswordLink, requestPasswordReset, setPasswordWithLink } from "./methods/passwordLink.js";
|
|
6
|
+
import { settings } from "./methods/settings.js";
|
|
5
7
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
6
8
|
import { signInWithGoogle } from "./methods/social.js";
|
|
7
9
|
import "@tanstack/react-start/server-only";
|
|
@@ -20,6 +22,9 @@ declare const auth: {
|
|
|
20
22
|
signInWithCode: typeof signInWithCode;
|
|
21
23
|
signInWithGoogle: typeof signInWithGoogle;
|
|
22
24
|
signInWithMagicLink: typeof signInWithMagicLink;
|
|
25
|
+
requestPasswordReset: typeof requestPasswordReset;
|
|
26
|
+
checkPasswordLink: typeof checkPasswordLink;
|
|
27
|
+
setPasswordWithLink: typeof setPasswordWithLink;
|
|
23
28
|
startAuthenticatorSetup: typeof startAuthenticatorSetup;
|
|
24
29
|
confirmAuthenticatorSetup: typeof confirmAuthenticatorSetup;
|
|
25
30
|
skipAuthenticatorSetup: typeof skipAuthenticatorSetup;
|
|
@@ -27,6 +32,7 @@ declare const auth: {
|
|
|
27
32
|
sendAuthenticatorResetCode: typeof sendAuthenticatorResetCode;
|
|
28
33
|
resetAuthenticator: typeof resetAuthenticator;
|
|
29
34
|
removeAuthenticator: typeof removeAuthenticator;
|
|
35
|
+
settings: typeof settings;
|
|
30
36
|
};
|
|
31
37
|
//#endregion
|
|
32
38
|
export { auth };
|
package/dist/auth/server.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { settings } from "./methods/settings.js";
|
|
1
2
|
import { confirmAuthenticatorSetup, removeAuthenticator, resetAuthenticator, sendAuthenticatorResetCode, skipAuthenticatorSetup, startAuthenticatorSetup, verifyAuthenticator } from "./methods/authenticator.js";
|
|
2
3
|
import { sendSignInCode, signInWithCode } from "./methods/code.js";
|
|
3
4
|
import { signInWithMagicLink } from "./methods/magicLink.js";
|
|
4
5
|
import { signIn, signOut } from "./methods/password.js";
|
|
6
|
+
import { checkPasswordLink, requestPasswordReset, setPasswordWithLink } from "./methods/passwordLink.js";
|
|
5
7
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
6
8
|
import { signInWithGoogle } from "./methods/social.js";
|
|
7
9
|
import "@tanstack/react-start/server-only";
|
|
@@ -19,13 +21,17 @@ const auth = {
|
|
|
19
21
|
signInWithCode,
|
|
20
22
|
signInWithGoogle,
|
|
21
23
|
signInWithMagicLink,
|
|
24
|
+
requestPasswordReset,
|
|
25
|
+
checkPasswordLink,
|
|
26
|
+
setPasswordWithLink,
|
|
22
27
|
startAuthenticatorSetup,
|
|
23
28
|
confirmAuthenticatorSetup,
|
|
24
29
|
skipAuthenticatorSetup,
|
|
25
30
|
verifyAuthenticator,
|
|
26
31
|
sendAuthenticatorResetCode,
|
|
27
32
|
resetAuthenticator,
|
|
28
|
-
removeAuthenticator
|
|
33
|
+
removeAuthenticator,
|
|
34
|
+
settings
|
|
29
35
|
};
|
|
30
36
|
|
|
31
37
|
//#endregion
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { FileItem, useFileUpload } from "./core/use-file-upload.js";
|
|
2
|
-
import { useCommentComposer } from "./comments/use-comment-composer.js";
|
|
3
2
|
import "@tanstack/react-start/client-only";
|
|
4
|
-
export { type FileItem,
|
|
3
|
+
export { type FileItem, useFileUpload };
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DatasourceAction } from "../action.js";
|
|
2
2
|
|
|
3
3
|
//#region src/core/file-upload.d.ts
|
|
4
4
|
|
|
@@ -10,12 +10,7 @@ type FileAttachment = {
|
|
|
10
10
|
url: string;
|
|
11
11
|
filename: string;
|
|
12
12
|
id: string;
|
|
13
|
-
type?: string | null;
|
|
14
13
|
};
|
|
15
|
-
|
|
16
|
-
* A COMMENTS action uploads a comment attachment.
|
|
17
|
-
* The platform checks the comment settings.
|
|
18
|
-
*/
|
|
19
|
-
declare function upload(action: DatasourceAction | CommentsAction, file: File): Promise<FileAttachment>;
|
|
14
|
+
declare function upload(action: DatasourceAction, file: File): Promise<FileAttachment>;
|
|
20
15
|
//#endregion
|
|
21
16
|
export { FileAttachment, upload };
|
package/dist/core/file-upload.js
CHANGED
|
@@ -5,10 +5,6 @@ import { platformApiUrl } from "../auth/platform.js";
|
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
//#region src/core/file-upload.ts
|
|
8
|
-
/**
|
|
9
|
-
* A COMMENTS action uploads a comment attachment.
|
|
10
|
-
* The platform checks the comment settings.
|
|
11
|
-
*/
|
|
12
8
|
async function upload(action, file) {
|
|
13
9
|
const { applicationId } = sessionConfig();
|
|
14
10
|
const baseUrl = await platformApiUrl();
|
|
@@ -27,8 +23,7 @@ async function upload(action, file) {
|
|
|
27
23
|
return {
|
|
28
24
|
url: resultJSON.url,
|
|
29
25
|
filename: resultJSON.filename || "Untitled file",
|
|
30
|
-
id: resultJSON.id || randomUUID()
|
|
31
|
-
type: resultJSON.type ?? null
|
|
26
|
+
id: resultJSON.id || randomUUID()
|
|
32
27
|
};
|
|
33
28
|
}
|
|
34
29
|
|