@softr/sdk 0.0.64-alpha.1 → 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 +4 -63
- 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/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/softr-config.js +5 -26
- package/dist/visibility/route.d.ts +6 -34
- package/dist/visibility/route.js +13 -55
- 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 -131
- 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
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: sub-skill
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.64
|
|
14
|
+
library_version: 0.0.64
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineMutation` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.64
|
|
16
|
+
library_version: 0.0.64
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler or a server-side route hook — importing it into a component fails the build. The same holds for any helper that calls `ds`: declare it inside the handler. A function at module level that calls `ds` keeps the server-only import in the browser bundle, and every page stops loading until it moves. `defineQuery` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.64
|
|
16
|
+
library_version: 0.0.64
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
@@ -115,12 +115,6 @@ Every app declares where a denied visitor goes in `softr.json`, at the app root,
|
|
|
115
115
|
{
|
|
116
116
|
"$schema": "./node_modules/@softr/sdk/schema/softr.schema.json",
|
|
117
117
|
"auth": {
|
|
118
|
-
"routes": {
|
|
119
|
-
"login": { "path": "/login", "redirectParam": "redirect" },
|
|
120
|
-
"resetPassword": { "path": "/reset-password", "tokenParam": "token" },
|
|
121
|
-
"acceptInvitation": { "path": "/accept-invitation", "tokenParam": "token" },
|
|
122
|
-
"magicLink": { "path": "/magic-authentication", "tokenParam": "magic-token" }
|
|
123
|
-
},
|
|
124
118
|
"guard": {
|
|
125
119
|
"unauthenticated": "/login",
|
|
126
120
|
"alreadyAuthenticated": "/",
|
|
@@ -130,17 +124,13 @@ Every app declares where a denied visitor goes in `softr.json`, at the app root,
|
|
|
130
124
|
}
|
|
131
125
|
```
|
|
132
126
|
|
|
133
|
-
|
|
127
|
+
All three reasons are required, because nothing fills in a reason the file leaves out:
|
|
134
128
|
|
|
135
|
-
| Reason | Condition |
|
|
136
|
-
| --- | --- |
|
|
137
|
-
| `unauthenticated` | Route needs a visitor, none present |
|
|
138
|
-
| `forbidden` | Signed in, in none of the named groups |
|
|
139
|
-
| `alreadyAuthenticated` | Signed-in visitor on a signed-out-only route |
|
|
140
|
-
|
|
141
|
-
`guard.unauthenticated` is where signed-out visitors go. It is usually the same as `routes.login.path`, but it can be another page, such as a public landing page. Every `unauthenticated` redirect, an override's included, carries the page they wanted under `routes.login.redirectParam`. The login page reads it back with `toReturnPath`. See [auth/SKILL.md](../auth/SKILL.md).
|
|
142
|
-
|
|
143
|
-
`auth.routes` names the app's sign-in pages. To move one, rename its route file and edit its `path` in `auth.routes` to match. When `guard.unauthenticated` points at the login page, edit it too.
|
|
129
|
+
| Reason | Condition |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `unauthenticated` | Route needs a visitor, none present |
|
|
132
|
+
| `forbidden` | Signed in, in none of the named groups |
|
|
133
|
+
| `alreadyAuthenticated` | Signed-in visitor on a signed-out-only route |
|
|
144
134
|
|
|
145
135
|
That file covers every route at once; a route's own overrides win over it. A rule naming both audiences with no `customGroups` admits everyone, so it never redirects and never throws. Only `strategy: 'throw'` reaches an `errorComponent`. A redirect navigates away and the route never renders.
|
|
146
136
|
|
|
@@ -172,22 +162,20 @@ Use the predicates: `isAccessError` for any of the three, and `isUnauthenticated
|
|
|
172
162
|
|
|
173
163
|
### Run the check and then keep going
|
|
174
164
|
|
|
175
|
-
`guardRoute` covers almost everything. When a route needs its own logic afterwards, `assertRouteAccess(visibility,
|
|
165
|
+
`guardRoute` covers almost everything. When a route needs its own logic afterwards, `assertRouteAccess(visibility, context)` applies the same rule and throws the same access error, using the default behaviour for each reason. It takes no overrides.
|
|
176
166
|
|
|
177
|
-
It also narrows `
|
|
167
|
+
It also narrows `context.user` for the rest of the handler, where the set names one audience and no other: `User` under `['LOGGED_IN_USERS']`, `null` under `['NON_LOGGED_IN_USERS']`. A set naming both admits either kind of visitor, so it narrows to neither. `guardRoute` cannot, because it hands `beforeLoad` a function rather than running inside one.
|
|
178
168
|
|
|
179
169
|
```ts
|
|
180
170
|
import { redirect } from '@tanstack/react-router'
|
|
181
171
|
import { assertRouteAccess } from '@softr/sdk'
|
|
182
172
|
|
|
183
|
-
beforeLoad: (
|
|
184
|
-
assertRouteAccess(visibility,
|
|
185
|
-
if (
|
|
173
|
+
beforeLoad: ({ context }) => {
|
|
174
|
+
assertRouteAccess(visibility, context)
|
|
175
|
+
if (context.user.email.endsWith('@example.com')) throw redirect({ to: '/internal' })
|
|
186
176
|
},
|
|
187
177
|
```
|
|
188
178
|
|
|
189
|
-
Take `opts` whole. A `context` destructured in the parameter list does not narrow.
|
|
190
|
-
|
|
191
179
|
Reach for `assertRouteAccess` when the route wants the error thrown for it, `isAuthorized(visibility, context)` when it wants the same narrowing without a throw, and `checkAccess(visibility, context)` when it needs to branch on why. `checkAccess` returns `{ ok: true } | { ok: false, reason }` and throws nothing. All of them are synchronous and make no request.
|
|
192
180
|
|
|
193
181
|
`guardRoute` and `assertRouteAccess` throw router redirects, so they belong in a route and nowhere else. That is what `Route` in their names marks. `isAuthorized` and `checkAccess` throw nothing and answer anywhere, in a loader, a component or a server function.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.64
|
|
12
|
+
library_version: 0.0.64
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**Boundary.** `workflow` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineWorkflow` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -1,47 +0,0 @@
|
|
|
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 };
|
|
@@ -1,86 +0,0 @@
|
|
|
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 };
|
|
@@ -1,11 +0,0 @@
|
|
|
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 };
|
|
@@ -1,17 +0,0 @@
|
|
|
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 };
|
package/dist/auth/platform.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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/softr-config.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|