@softr/sdk 0.0.64-alpha.0 → 0.0.64
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/auth/isomorphic.d.ts +1 -60
- package/dist/auth/isomorphic.js +9 -65
- package/dist/auth/methods/signup.js +0 -1
- package/dist/auth/methods/social.js +0 -1
- package/dist/auth/methods/utils.d.ts +1 -1
- package/dist/auth/platform.js +0 -54
- package/dist/auth/server.d.ts +0 -6
- package/dist/auth/server.js +1 -7
- package/dist/default-error-overlay.d.ts +2 -2
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +8 -2
- package/dist/mcp.js +8 -8
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +2 -2
- package/dist/softr-config.js +5 -26
- package/dist/visibility/route.d.ts +6 -26
- package/dist/visibility/route.js +13 -36
- package/package.json +10 -33
- package/schema/softr.schema.json +1 -42
- package/skills/softr-sdk/SKILL.md +3 -4
- package/skills/softr-sdk/auth/SKILL.md +31 -129
- 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 +1 -1
- 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 +14 -7
- 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 +12 -24
- package/skills/softr-sdk/workflows/SKILL.md +1 -1
- package/dist/auth/methods/passwordLink.d.ts +0 -47
- package/dist/auth/methods/passwordLink.js +0 -86
- package/dist/auth/methods/settings.d.ts +0 -11
- package/dist/auth/methods/settings.js +0 -17
- package/dist/auth/platform.d.ts +0 -46
- package/dist/softr-config.d.ts +0 -1
|
@@ -2,8 +2,6 @@ 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";
|
|
7
5
|
import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
8
6
|
|
|
9
7
|
//#region src/auth/isomorphic.d.ts
|
|
@@ -56,62 +54,5 @@ declare function initialSoftrContext(): SoftrContext;
|
|
|
56
54
|
* }
|
|
57
55
|
*/
|
|
58
56
|
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;
|
|
116
57
|
//#endregion
|
|
117
|
-
export {
|
|
58
|
+
export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser };
|
package/dist/auth/isomorphic.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { assert, panic
|
|
1
|
+
import { assert, panic } 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";
|
|
9
8
|
import { bootErrorMiddleware } from "../boot-error.js";
|
|
10
9
|
import { pwaMiddleware } from "../pwa.js";
|
|
11
|
-
import { createContext, useContext } from "react";
|
|
12
10
|
import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
|
|
13
11
|
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
14
12
|
import { rootRouteId, useRouteContext } from "@tanstack/react-router";
|
|
15
|
-
import { jsx } from "react/jsx-runtime";
|
|
16
13
|
|
|
17
|
-
//#region src/auth/isomorphic.
|
|
14
|
+
//#region src/auth/isomorphic.ts
|
|
18
15
|
/**
|
|
19
16
|
* Resolves the Softr visitor, their permissions and the app's formatting for
|
|
20
17
|
* every request, so that `getSoftrContext` and the route guards have something
|
|
@@ -32,8 +29,10 @@ function softrAuthMiddleware() {
|
|
|
32
29
|
return createMiddleware({ type: "request" }).middleware([bootErrorMiddleware(), pwaMiddleware()]).server(async ({ next }) => {
|
|
33
30
|
async function getSession() {
|
|
34
31
|
const token = readSessionToken();
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (process.env.NODE_ENV === "production") {
|
|
33
|
+
const cached = readSessionData(token);
|
|
34
|
+
if (cached !== null) return cached;
|
|
35
|
+
}
|
|
37
36
|
const result = await makePlatform(sessionConfig().applicationId).session(token);
|
|
38
37
|
if (!result.ok) panic(result.message);
|
|
39
38
|
const session = result.data;
|
|
@@ -44,11 +43,11 @@ function softrAuthMiddleware() {
|
|
|
44
43
|
writeCookies([makeSessionDataCookie(session, token)]);
|
|
45
44
|
return session;
|
|
46
45
|
}
|
|
47
|
-
const [{ user, permissions }, settings
|
|
46
|
+
const [{ user, permissions }, settings] = await Promise.all([getSession(), readLocaleSettings()]);
|
|
48
47
|
return next({ context: {
|
|
49
48
|
user,
|
|
50
49
|
permissions,
|
|
51
|
-
formatting: resolveFormatting(settings
|
|
50
|
+
formatting: resolveFormatting(settings, getRequestHeader("accept-language") ?? null, readUserTimezone())
|
|
52
51
|
} });
|
|
53
52
|
});
|
|
54
53
|
}
|
|
@@ -97,61 +96,6 @@ function useCurrentUser() {
|
|
|
97
96
|
select: (context) => context.user
|
|
98
97
|
});
|
|
99
98
|
}
|
|
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
|
-
}
|
|
155
99
|
|
|
156
100
|
//#endregion
|
|
157
|
-
export {
|
|
101
|
+
export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser };
|
|
@@ -17,7 +17,6 @@ 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);
|
|
21
20
|
if (result.status >= 500) panic(`Expected the sign-up code to be emailed, but got ${result.status}: ` + result.message);
|
|
22
21
|
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
23
22
|
}
|
|
@@ -22,7 +22,6 @@ 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);
|
|
26
25
|
if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
|
|
27
26
|
if (result.status >= 500) panic(`Expected Google sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
28
27
|
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";
|
|
14
14
|
//#endregion
|
|
15
15
|
export { AuthError, Result };
|
package/dist/auth/platform.js
CHANGED
|
@@ -6,35 +6,8 @@ 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
|
-
});
|
|
35
9
|
function makePlatform(applicationId) {
|
|
36
10
|
return {
|
|
37
|
-
authSettings: () => request(`/v1/vibe/applications/${applicationId}/users/auth-settings`, { method: "GET" }, authSettingsSchema),
|
|
38
11
|
session: async (token) => {
|
|
39
12
|
const result = await request(`/v1/vibe/applications/${applicationId}/users/session`, {
|
|
40
13
|
method: "GET",
|
|
@@ -126,33 +99,6 @@ function makePlatform(applicationId) {
|
|
|
126
99
|
challengeToken: z.string()
|
|
127
100
|
}).nullish()
|
|
128
101
|
})),
|
|
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
|
-
})),
|
|
156
102
|
sendSignUpCode: (body) => request(`/v1/vibe/applications/${applicationId}/users/sign-up/send-code`, {
|
|
157
103
|
method: "POST",
|
|
158
104
|
json: body
|
package/dist/auth/server.d.ts
CHANGED
|
@@ -2,8 +2,6 @@ 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";
|
|
7
5
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
8
6
|
import { signInWithGoogle } from "./methods/social.js";
|
|
9
7
|
import "@tanstack/react-start/server-only";
|
|
@@ -22,9 +20,6 @@ declare const auth: {
|
|
|
22
20
|
signInWithCode: typeof signInWithCode;
|
|
23
21
|
signInWithGoogle: typeof signInWithGoogle;
|
|
24
22
|
signInWithMagicLink: typeof signInWithMagicLink;
|
|
25
|
-
requestPasswordReset: typeof requestPasswordReset;
|
|
26
|
-
checkPasswordLink: typeof checkPasswordLink;
|
|
27
|
-
setPasswordWithLink: typeof setPasswordWithLink;
|
|
28
23
|
startAuthenticatorSetup: typeof startAuthenticatorSetup;
|
|
29
24
|
confirmAuthenticatorSetup: typeof confirmAuthenticatorSetup;
|
|
30
25
|
skipAuthenticatorSetup: typeof skipAuthenticatorSetup;
|
|
@@ -32,7 +27,6 @@ declare const auth: {
|
|
|
32
27
|
sendAuthenticatorResetCode: typeof sendAuthenticatorResetCode;
|
|
33
28
|
resetAuthenticator: typeof resetAuthenticator;
|
|
34
29
|
removeAuthenticator: typeof removeAuthenticator;
|
|
35
|
-
settings: typeof settings;
|
|
36
30
|
};
|
|
37
31
|
//#endregion
|
|
38
32
|
export { auth };
|
package/dist/auth/server.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { settings } from "./methods/settings.js";
|
|
2
1
|
import { confirmAuthenticatorSetup, removeAuthenticator, resetAuthenticator, sendAuthenticatorResetCode, skipAuthenticatorSetup, startAuthenticatorSetup, verifyAuthenticator } from "./methods/authenticator.js";
|
|
3
2
|
import { sendSignInCode, signInWithCode } from "./methods/code.js";
|
|
4
3
|
import { signInWithMagicLink } from "./methods/magicLink.js";
|
|
5
4
|
import { signIn, signOut } from "./methods/password.js";
|
|
6
|
-
import { checkPasswordLink, requestPasswordReset, setPasswordWithLink } from "./methods/passwordLink.js";
|
|
7
5
|
import { confirmSignUp, signUp } from "./methods/signup.js";
|
|
8
6
|
import { signInWithGoogle } from "./methods/social.js";
|
|
9
7
|
import "@tanstack/react-start/server-only";
|
|
@@ -21,17 +19,13 @@ const auth = {
|
|
|
21
19
|
signInWithCode,
|
|
22
20
|
signInWithGoogle,
|
|
23
21
|
signInWithMagicLink,
|
|
24
|
-
requestPasswordReset,
|
|
25
|
-
checkPasswordLink,
|
|
26
|
-
setPasswordWithLink,
|
|
27
22
|
startAuthenticatorSetup,
|
|
28
23
|
confirmAuthenticatorSetup,
|
|
29
24
|
skipAuthenticatorSetup,
|
|
30
25
|
verifyAuthenticator,
|
|
31
26
|
sendAuthenticatorResetCode,
|
|
32
27
|
resetAuthenticator,
|
|
33
|
-
removeAuthenticator
|
|
34
|
-
settings
|
|
28
|
+
removeAuthenticator
|
|
35
29
|
};
|
|
36
30
|
|
|
37
31
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react5 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):
|
|
11
|
+
}: Props): react5.JSX.Element;
|
|
12
12
|
//#endregion
|
|
13
13
|
export { DefaultErrorOverlay };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,12 @@ 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";
|
|
8
7
|
import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
|
|
9
|
-
import { assertRouteAccess,
|
|
10
|
-
import {
|
|
8
|
+
import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
|
|
9
|
+
import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
|
|
11
10
|
import { softrMcpMiddleware } from "./mcp.js";
|
|
12
11
|
import { useUserTimezone } from "./timezone.js";
|
|
13
12
|
import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
|
|
14
13
|
import { DefaultErrorOverlay } from "./default-error-overlay.js";
|
|
15
14
|
import { Audience } from "./visibility/audience.js";
|
|
16
|
-
export { type ActionPermissions, Audience, type
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
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
|
-
import { assertRouteAccess,
|
|
4
|
+
import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
|
|
5
5
|
import { FormattedDate, FormattingScope, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
|
|
6
6
|
import { SoftrPwaHead, usePwaInstall } from "./pwa.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
|
|
8
8
|
import { softrMcpMiddleware } from "./mcp.js";
|
|
9
9
|
import { useUserTimezone } from "./timezone.js";
|
|
10
10
|
import { DefaultErrorOverlay } from "./default-error-overlay.js";
|
|
11
11
|
import { Audience } from "./visibility/audience.js";
|
|
12
12
|
|
|
13
|
-
export { Audience,
|
|
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 };
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatasourceAction } from "./action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _tanstack_react_start3 from "@tanstack/react-start";
|
|
3
3
|
|
|
4
4
|
//#region src/mcp.d.ts
|
|
5
5
|
|
|
@@ -12,6 +12,12 @@ type SoftrMcpOptions = {
|
|
|
12
12
|
* decide who may run the ones that are listed.
|
|
13
13
|
*/
|
|
14
14
|
actions?: Array<DatasourceAction>;
|
|
15
|
+
/**
|
|
16
|
+
* The app's sign-in page, as a path on this app. A signed-out visitor who
|
|
17
|
+
* opens the approval page is sent here with `?redirect=` pointing back at it.
|
|
18
|
+
* Defaults to `/login`.
|
|
19
|
+
*/
|
|
20
|
+
loginPath?: string;
|
|
15
21
|
};
|
|
16
22
|
/**
|
|
17
23
|
* Serves the MCP endpoint and its OAuth flow from the app's own origin.
|
|
@@ -31,6 +37,6 @@ type SoftrMcpOptions = {
|
|
|
31
37
|
* ],
|
|
32
38
|
* }))
|
|
33
39
|
*/
|
|
34
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions):
|
|
40
|
+
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start3.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
35
41
|
//#endregion
|
|
36
42
|
export { softrMcpMiddleware };
|
package/dist/mcp.js
CHANGED
|
@@ -4,7 +4,6 @@ import { sessionConfig } from "./auth/config.js";
|
|
|
4
4
|
import { readSessionToken, withSessionToken } from "./auth/session.js";
|
|
5
5
|
import { makePlatform, platformApiUrl } from "./auth/platform.js";
|
|
6
6
|
import { isDatasourceAction } from "./action.js";
|
|
7
|
-
import { softrConfig } from "./visibility/route.js";
|
|
8
7
|
import { ds } from "./datasource/datasource.js";
|
|
9
8
|
import { createMiddleware } from "@tanstack/react-start";
|
|
10
9
|
import { z } from "zod";
|
|
@@ -39,7 +38,8 @@ const SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26"];
|
|
|
39
38
|
* }))
|
|
40
39
|
*/
|
|
41
40
|
function softrMcpMiddleware(options = {}) {
|
|
42
|
-
const
|
|
41
|
+
const loginPath = options.loginPath ?? "/login";
|
|
42
|
+
assert(loginPath.startsWith("/") && new URL(loginPath, "https://app.invalid").origin === "https://app.invalid", `softrMcpMiddleware's loginPath must be a path on this app, got "${loginPath}"`);
|
|
43
43
|
const actions = /* @__PURE__ */ new Map();
|
|
44
44
|
for (const action of options.actions ?? []) {
|
|
45
45
|
assert(isDatasourceAction(action), "softrMcpMiddleware takes actions built by defineQuery or defineMutation");
|
|
@@ -68,7 +68,7 @@ function softrMcpMiddleware(options = {}) {
|
|
|
68
68
|
client_id_metadata_document_supported: true,
|
|
69
69
|
scopes_supported: SCOPES
|
|
70
70
|
});
|
|
71
|
-
case AUTHORIZE_PATH: return await authorize(request,
|
|
71
|
+
case AUTHORIZE_PATH: return await authorize(request, loginPath);
|
|
72
72
|
case TOKEN_PATH: return await issueToken(request);
|
|
73
73
|
case MCP_PATH: return await rpc(request, origin, actions);
|
|
74
74
|
default: return await next();
|
|
@@ -92,14 +92,14 @@ const authorizeQuerySchema = z.object({
|
|
|
92
92
|
* because this runs on the app's origin. The client and the code are the
|
|
93
93
|
* platform's, so we ask it for both.
|
|
94
94
|
*/
|
|
95
|
-
async function authorize(request,
|
|
95
|
+
async function authorize(request, loginPath) {
|
|
96
96
|
const url = new URL(request.url);
|
|
97
97
|
const query = authorizeQuerySchema.safeParse(Object.fromEntries(url.searchParams));
|
|
98
98
|
if (!query.success) return html(400, page("This request is not valid", "The app that sent you here left something out. Close this window and try connecting again."));
|
|
99
99
|
if (request.method !== "GET" && request.method !== "POST") return methodNotAllowed("GET, POST");
|
|
100
100
|
const sessionToken = readSessionToken();
|
|
101
101
|
const user = sessionToken === null ? null : await resolveUser(sessionToken);
|
|
102
|
-
if (user === null || sessionToken === null) return html(200, signInPage(url,
|
|
102
|
+
if (user === null || sessionToken === null) return html(200, signInPage(url, loginPath));
|
|
103
103
|
const client = await platformConsent(query.data);
|
|
104
104
|
if (!client.ok) return html(400, page("We can't connect this right now", escapeHtml(client.message)));
|
|
105
105
|
if (request.method === "GET") return html(200, consentPage(user, url, client.name));
|
|
@@ -539,9 +539,9 @@ function consentPage(user, url, clientName) {
|
|
|
539
539
|
<button type="submit" name="decision" value="deny">Cancel</button>
|
|
540
540
|
</form>`);
|
|
541
541
|
}
|
|
542
|
-
function signInPage(url,
|
|
543
|
-
const back =
|
|
544
|
-
return page("Sign in first", "You need to be signed in to this app before you can connect an assistant to it.", `<a class="primary" href="${escapeHtml(
|
|
542
|
+
function signInPage(url, loginPath) {
|
|
543
|
+
const back = encodeURIComponent(url.pathname + url.search);
|
|
544
|
+
return page("Sign in first", "You need to be signed in to this app before you can connect an assistant to it.", `<a class="primary" href="${escapeHtml(loginPath)}?redirect=${back}">Sign in</a>`);
|
|
545
545
|
}
|
|
546
546
|
function page(title, body, actions = "") {
|
|
547
547
|
return `<!doctype html>
|
package/dist/package.js
CHANGED
package/dist/pwa.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react3 from "react";
|
|
2
2
|
import "@tanstack/react-start";
|
|
3
3
|
|
|
4
4
|
//#region src/pwa.d.ts
|
|
@@ -23,7 +23,7 @@ import "@tanstack/react-start";
|
|
|
23
23
|
* )
|
|
24
24
|
* }
|
|
25
25
|
*/
|
|
26
|
-
declare function SoftrPwaHead():
|
|
26
|
+
declare function SoftrPwaHead(): react3.FunctionComponentElement<react3.FragmentProps>;
|
|
27
27
|
type PwaPlatform = "ios" | "android" | "desktop";
|
|
28
28
|
type PwaInstall = {
|
|
29
29
|
available: false;
|
package/dist/softr-config.js
CHANGED
|
@@ -9,32 +9,11 @@ const AccessBehaviour = z.union([
|
|
|
9
9
|
}),
|
|
10
10
|
z.object({ strategy: z.literal("throw") })
|
|
11
11
|
], { error: "must be a path like \"/login\", { \"strategy\": \"redirect\", \"to\": \"/login\" } or { \"strategy\": \"throw\" }" });
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
redirectParam: z.string()
|
|
18
|
-
}),
|
|
19
|
-
resetPassword: z.strictObject({
|
|
20
|
-
path: AppPath,
|
|
21
|
-
tokenParam: z.string()
|
|
22
|
-
}),
|
|
23
|
-
acceptInvitation: z.strictObject({
|
|
24
|
-
path: AppPath,
|
|
25
|
-
tokenParam: z.string()
|
|
26
|
-
}),
|
|
27
|
-
magicLink: z.strictObject({
|
|
28
|
-
path: AppPath,
|
|
29
|
-
tokenParam: z.string()
|
|
30
|
-
})
|
|
31
|
-
}),
|
|
32
|
-
guard: z.strictObject({
|
|
33
|
-
unauthenticated: AccessBehaviour,
|
|
34
|
-
forbidden: AccessBehaviour,
|
|
35
|
-
alreadyAuthenticated: AccessBehaviour
|
|
36
|
-
})
|
|
37
|
-
}) });
|
|
12
|
+
const SoftrConfigSchema = z.object({ auth: z.strictObject({ guard: z.strictObject({
|
|
13
|
+
unauthenticated: AccessBehaviour,
|
|
14
|
+
forbidden: AccessBehaviour,
|
|
15
|
+
alreadyAuthenticated: AccessBehaviour
|
|
16
|
+
}) }) });
|
|
38
17
|
|
|
39
18
|
//#endregion
|
|
40
19
|
export { SoftrConfigSchema };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AccessContext, AccessReason, Narrowed, Visibility, VisibilityRule } from "./visibility.js";
|
|
2
|
-
import "../softr-config.js";
|
|
3
2
|
|
|
4
3
|
//#region src/visibility/route.d.ts
|
|
5
4
|
|
|
@@ -47,8 +46,7 @@ declare function isAlreadyAuthenticatedError(error: unknown): error is AccessErr
|
|
|
47
46
|
* A `beforeLoad` handler that denies a visitor who fails the rule.
|
|
48
47
|
*
|
|
49
48
|
* Each reason falls back to the app's configured behaviour. Pass `overrides` to
|
|
50
|
-
* change what any one reason does.
|
|
51
|
-
* page they wanted, for {@link authRedirectTarget} to read back.
|
|
49
|
+
* change what any one reason does.
|
|
52
50
|
*
|
|
53
51
|
* Throws a router redirect, so it belongs in `beforeLoad` and nowhere else.
|
|
54
52
|
*
|
|
@@ -57,35 +55,17 @@ declare function isAlreadyAuthenticatedError(error: unknown): error is AccessErr
|
|
|
57
55
|
*/
|
|
58
56
|
declare function guardRoute(visibility: Visibility, overrides?: GuardOverrides): (opts: {
|
|
59
57
|
context: AccessContext;
|
|
60
|
-
location: {
|
|
61
|
-
href: string;
|
|
62
|
-
};
|
|
63
58
|
}) => void;
|
|
64
59
|
/**
|
|
65
60
|
* Throws what {@link guardRoute} would, for a `beforeLoad` that runs its own
|
|
66
61
|
* checks after the rule. It takes no overrides.
|
|
67
62
|
*
|
|
68
63
|
* @example
|
|
69
|
-
* beforeLoad: (
|
|
70
|
-
* assertRouteAccess(visibility,
|
|
71
|
-
* if (
|
|
64
|
+
* beforeLoad: ({ context, params }) => {
|
|
65
|
+
* assertRouteAccess(visibility, context)
|
|
66
|
+
* if (params.projectId === "demo") throw redirect({ to: "/projects" })
|
|
72
67
|
* }
|
|
73
68
|
*/
|
|
74
|
-
declare function assertRouteAccess<R extends VisibilityRule
|
|
75
|
-
context: AccessContext;
|
|
76
|
-
location: {
|
|
77
|
-
href: string;
|
|
78
|
-
};
|
|
79
|
-
}>(visibility: Visibility<R>, opts: O): asserts opts is O & {
|
|
80
|
-
context: Narrowed<R>;
|
|
81
|
-
};
|
|
82
|
-
/**
|
|
83
|
-
* Where to send a visitor who just signed in: the path a guard sent them away
|
|
84
|
-
* from, or `home`. Anything but a path on this app is `home`.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* const target = authRedirectTarget(useLocation({ select: (l) => l.search }), "/")
|
|
88
|
-
*/
|
|
89
|
-
declare function authRedirectTarget(search: Record<string, unknown>, home: string): string;
|
|
69
|
+
declare function assertRouteAccess<R extends VisibilityRule>(visibility: Visibility<R>, context: AccessContext): asserts context is Narrowed<R>;
|
|
90
70
|
//#endregion
|
|
91
|
-
export { assertRouteAccess,
|
|
71
|
+
export { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError };
|