@softr/sdk 0.0.65-alpha.0 → 0.0.65

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.
Files changed (43) hide show
  1. package/dist/auth/isomorphic.d.ts +60 -1
  2. package/dist/auth/isomorphic.js +67 -7
  3. package/dist/auth/methods/passwordLink.d.ts +47 -0
  4. package/dist/auth/methods/passwordLink.js +86 -0
  5. package/dist/auth/methods/settings.d.ts +11 -0
  6. package/dist/auth/methods/settings.js +17 -0
  7. package/dist/auth/methods/signup.js +1 -0
  8. package/dist/auth/methods/social.js +1 -0
  9. package/dist/auth/methods/utils.d.ts +1 -1
  10. package/dist/auth/platform.d.ts +46 -0
  11. package/dist/auth/platform.js +54 -0
  12. package/dist/auth/returnPath.d.ts +19 -0
  13. package/dist/auth/returnPath.js +37 -0
  14. package/dist/auth/server.d.ts +6 -0
  15. package/dist/auth/server.js +7 -1
  16. package/dist/default-error-overlay.d.ts +2 -2
  17. package/dist/index.d.ts +4 -2
  18. package/dist/index.js +3 -2
  19. package/dist/mcp.d.ts +2 -8
  20. package/dist/mcp.js +8 -8
  21. package/dist/package.js +1 -1
  22. package/dist/pwa.d.ts +2 -2
  23. package/dist/softr-config.js +5 -15
  24. package/dist/softr-config.schema.js +40 -0
  25. package/dist/visibility/route.d.ts +16 -5
  26. package/dist/visibility/route.js +21 -13
  27. package/dist/vite.js +1 -1
  28. package/package.json +10 -33
  29. package/schema/softr.schema.json +42 -1
  30. package/skills/softr-sdk/SKILL.md +4 -3
  31. package/skills/softr-sdk/auth/SKILL.md +131 -31
  32. package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
  33. package/skills/softr-sdk/current-user/SKILL.md +1 -1
  34. package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
  35. package/skills/softr-sdk/formatting/SKILL.md +4 -4
  36. package/skills/softr-sdk/integrations/SKILL.md +1 -1
  37. package/skills/softr-sdk/mcp/SKILL.md +7 -14
  38. package/skills/softr-sdk/pwa/SKILL.md +3 -3
  39. package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
  40. package/skills/softr-sdk/record-queries/SKILL.md +2 -3
  41. package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
  42. package/skills/softr-sdk/routes-and-visibility/SKILL.md +25 -14
  43. package/skills/softr-sdk/workflows/SKILL.md +1 -1
@@ -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 };
@@ -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.ts
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
@@ -29,8 +32,10 @@ function softrAuthMiddleware() {
29
32
  return createMiddleware({ type: "request" }).middleware([bootErrorMiddleware(), pwaMiddleware()]).server(async ({ next }) => {
30
33
  async function getSession() {
31
34
  const token = readSessionToken();
32
- const cached = readSessionData(token);
33
- if (cached !== null) return cached;
35
+ if (process.env.NODE_ENV === "production") {
36
+ const cached = readSessionData(token);
37
+ if (cached !== null) return cached;
38
+ }
34
39
  const result = await makePlatform(sessionConfig().applicationId).session(token);
35
40
  if (!result.ok) panic(result.message);
36
41
  const session = result.data;
@@ -41,11 +46,11 @@ function softrAuthMiddleware() {
41
46
  writeCookies([makeSessionDataCookie(session, token)]);
42
47
  return session;
43
48
  }
44
- const [{ user, permissions }, settings] = await Promise.all([getSession(), readLocaleSettings()]);
49
+ const [{ user, permissions }, settings$1] = await Promise.all([getSession(), readLocaleSettings()]);
45
50
  return next({ context: {
46
51
  user,
47
52
  permissions,
48
- formatting: resolveFormatting(settings, getRequestHeader("accept-language") ?? null, readUserTimezone())
53
+ formatting: resolveFormatting(settings$1, getRequestHeader("accept-language") ?? null, readUserTimezone())
49
54
  } });
50
55
  });
51
56
  }
@@ -94,6 +99,61 @@ function useCurrentUser() {
94
99
  select: (context) => context.user
95
100
  });
96
101
  }
102
+ /**
103
+ * Loads the app's {@link AuthOptions}. Call it from the auth layout's loader
104
+ * and hand the result to {@link AuthSettingsProvider}.
105
+ *
106
+ * @example
107
+ * export const Route = createFileRoute("/_auth")({
108
+ * loader: () => getAuthSettings(),
109
+ * staleTime: 60_000,
110
+ * })
111
+ */
112
+ const getAuthSettings = createServerFn({ method: "GET" }).handler(async () => {
113
+ const raw = await settings();
114
+ const on = (method) => raw.loginEnabled && raw.authenticationMethods.includes(method);
115
+ const emailMethods = on("EMAIL") ? raw.emailAuth?.loginMethods ?? [] : [];
116
+ const clientId = raw.googleAuth?.clientId?.trim();
117
+ return {
118
+ password: emailMethods.includes("PASSWORD"),
119
+ emailCode: emailMethods.includes("EMAIL_CODE"),
120
+ magicLink: on("MAGIC_LINK"),
121
+ google: on("GOOGLE") && clientId ? { clientId } : null,
122
+ signUp: raw.loginEnabled && (raw.signUpPolicy === "OPEN_TO_PUBLIC" || raw.signUpPolicy === "DOMAIN_RESTRICTED"),
123
+ strongPasswords: on("EMAIL") && (raw.emailAuth?.strongPasswords ?? false)
124
+ };
125
+ });
126
+ const AuthSettingsContext = createContext(null);
127
+ /**
128
+ * Makes the auth options readable with {@link useAuthSettings} anywhere below.
129
+ *
130
+ * @example
131
+ * function AuthLayout() {
132
+ * const authSettings = Route.useLoaderData()
133
+ * return (
134
+ * <AuthSettingsProvider value={authSettings}>
135
+ * <Outlet />
136
+ * </AuthSettingsProvider>
137
+ * )
138
+ * }
139
+ */
140
+ function AuthSettingsProvider({ value, children }) {
141
+ return /* @__PURE__ */ jsx(AuthSettingsContext.Provider, {
142
+ value,
143
+ children
144
+ });
145
+ }
146
+ /**
147
+ * Which ways in this app offers. Requires an {@link AuthSettingsProvider}
148
+ * above it.
149
+ *
150
+ * @example
151
+ * const options = useAuthSettings()
152
+ * return options.password && <PasswordForm />
153
+ */
154
+ function useAuthSettings() {
155
+ return requireDefined(useContext(AuthSettingsContext), "useAuthSettings needs an AuthSettingsProvider above it.");
156
+ }
97
157
 
98
158
  //#endregion
99
- export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser };
159
+ 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 };
@@ -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
@@ -0,0 +1,19 @@
1
+ //#region src/auth/returnPath.d.ts
2
+ /**
3
+ * Where to send a visitor who just signed in: the path a guard sent them away
4
+ * from, else where `auth.guard.alreadyAuthenticated` in softr.json points, else
5
+ * `/`. Anything but a path on this app counts as missing. It always reloads the
6
+ * page, so the router picks up the new session.
7
+ *
8
+ * @example
9
+ * const location = useLocation()
10
+ * navigate(toReturnPath(location))
11
+ */
12
+ declare function toReturnPath(location: {
13
+ search: Record<string, unknown>;
14
+ }): {
15
+ href: string;
16
+ reloadDocument: true;
17
+ };
18
+ //#endregion
19
+ export { toReturnPath };
@@ -0,0 +1,37 @@
1
+ import { softrConfig } from "../softr-config.js";
2
+
3
+ //#region src/auth/returnPath.ts
4
+ /**
5
+ * Where to send a visitor who just signed in: the path a guard sent them away
6
+ * from, else where `auth.guard.alreadyAuthenticated` in softr.json points, else
7
+ * `/`. Anything but a path on this app counts as missing. It always reloads the
8
+ * page, so the router picks up the new session.
9
+ *
10
+ * @example
11
+ * const location = useLocation()
12
+ * navigate(toReturnPath(location))
13
+ */
14
+ function toReturnPath(location) {
15
+ const { auth } = softrConfig();
16
+ const target = location.search[auth.routes.login.redirectParam];
17
+ if (typeof target === "string" && target.startsWith("/") && new URL(target, "https://app.invalid").origin === "https://app.invalid") return {
18
+ href: target,
19
+ reloadDocument: true
20
+ };
21
+ const fallback = auth.guard.alreadyAuthenticated;
22
+ if (typeof fallback === "string") return {
23
+ href: fallback,
24
+ reloadDocument: true
25
+ };
26
+ if (fallback.strategy === "redirect") return {
27
+ href: fallback.to,
28
+ reloadDocument: true
29
+ };
30
+ return {
31
+ href: "/",
32
+ reloadDocument: true
33
+ };
34
+ }
35
+
36
+ //#endregion
37
+ export { toReturnPath };
@@ -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 };
@@ -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
@@ -1,4 +1,4 @@
1
- import * as react5 from "react";
1
+ import * as react6 from "react";
2
2
 
3
3
  //#region src/default-error-overlay.d.ts
4
4
  type Props = {
@@ -8,6 +8,6 @@ type Props = {
8
8
  declare function DefaultErrorOverlay({
9
9
  error,
10
10
  reset
11
- }: Props): react5.JSX.Element;
11
+ }: Props): react6.JSX.Element;
12
12
  //#endregion
13
13
  export { DefaultErrorOverlay };
package/dist/index.d.ts CHANGED
@@ -4,12 +4,14 @@ import { FileAttachment } from "./core/file-upload.js";
4
4
  import { FileItem, useFileUpload } from "./core/use-file-upload.js";
5
5
  import { DateOptions, DateParts, DateSeparator, DateValue, FixedDateFormat, FormattedDate, Formatting, FormattingScope, WeekDay, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
6
6
  import { SoftrContext } from "./auth/context.js";
7
+ import { AuthSettings } from "./auth/platform.js";
7
8
  import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
8
9
  import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
9
- import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
10
+ import { toReturnPath } from "./auth/returnPath.js";
11
+ import { AuthOptions, AuthSettingsProvider, getAuthSettings, getSoftrContext, initialSoftrContext, softrAuthMiddleware, useAuthSettings, useCurrentUser } from "./auth/isomorphic.js";
10
12
  import { softrMcpMiddleware } from "./mcp.js";
11
13
  import { useUserTimezone } from "./timezone.js";
12
14
  import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
13
15
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
14
16
  import { Audience } from "./visibility/audience.js";
15
- export { type ActionPermissions, Audience, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type User, type WeekDay, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, formatDate, formatDateTime, formatNumber, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, useCurrentUser, useFileUpload, useFormatting, usePwaInstall, useUserTimezone };
17
+ export { type ActionPermissions, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type User, type WeekDay, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, formatDate, formatDateTime, formatNumber, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, usePwaInstall, useUserTimezone };
package/dist/index.js CHANGED
@@ -2,12 +2,13 @@ import { useFileUpload } from "./core/use-file-upload.js";
2
2
  import { defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
3
3
  import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
4
4
  import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
5
+ import { toReturnPath } from "./auth/returnPath.js";
5
6
  import { FormattedDate, FormattingScope, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
6
7
  import { SoftrPwaHead, usePwaInstall } from "./pwa.js";
7
- import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
8
+ import { AuthSettingsProvider, getAuthSettings, getSoftrContext, initialSoftrContext, softrAuthMiddleware, useAuthSettings, useCurrentUser } from "./auth/isomorphic.js";
8
9
  import { softrMcpMiddleware } from "./mcp.js";
9
10
  import { useUserTimezone } from "./timezone.js";
10
11
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
11
12
  import { Audience } from "./visibility/audience.js";
12
13
 
13
- export { Audience, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, formatDate, formatDateTime, formatNumber, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, useCurrentUser, useFileUpload, useFormatting, usePwaInstall, useUserTimezone };
14
+ export { Audience, AuthSettingsProvider, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, formatDate, formatDateTime, formatNumber, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, usePwaInstall, useUserTimezone };