@softr/sdk 0.0.24 → 0.0.26-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 +195 -0
- package/dist/action.js +21 -0
- package/dist/assertions.js +1 -7
- package/dist/auth/isomorphic.d.ts +1 -2
- package/dist/auth/methods/authenticator.d.ts +80 -0
- package/dist/auth/methods/authenticator.js +175 -0
- package/dist/auth/methods/code.d.ts +40 -0
- package/dist/auth/methods/code.js +74 -0
- package/dist/auth/methods/magicLink.d.ts +24 -0
- package/dist/auth/methods/magicLink.js +53 -0
- package/dist/auth/methods/password.d.ts +19 -5
- package/dist/auth/methods/password.js +36 -15
- package/dist/auth/methods/signup.d.ts +22 -14
- package/dist/auth/methods/signup.js +48 -54
- package/dist/auth/methods/social.d.ts +35 -0
- package/dist/auth/methods/social.js +58 -0
- package/dist/auth/methods/utils.d.ts +1 -1
- package/dist/auth/permissions.d.ts +7 -2
- package/dist/auth/platform.js +185 -7
- package/dist/auth/server.d.ts +15 -0
- package/dist/auth/server.js +16 -1
- package/dist/auth/session.js +35 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +23 -0
- package/dist/codegen/datasource-types.js +471 -0
- package/dist/codegen/fetch-metadata.js +46 -0
- package/dist/codegen/softr-env.js +102 -0
- package/dist/codegen/user-groups.js +11 -0
- package/dist/core/integration.js +1 -1
- package/dist/datasource/chart.d.ts +196 -0
- package/dist/datasource/chart.js +289 -0
- package/dist/datasource/datasource.d.ts +16 -0
- package/dist/datasource/datasource.js +17 -0
- package/dist/datasource/fields.d.ts +122 -0
- package/dist/datasource/fields.js +82 -0
- package/dist/datasource/filter.d.ts +30 -0
- package/dist/datasource/filter.js +146 -0
- package/dist/datasource/record.d.ts +138 -0
- package/dist/datasource/record.js +258 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +2 -2
- package/dist/package.js +5 -0
- package/dist/routes/visibility.d.ts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +31 -8
- package/skills/softr-sdk/SKILL.md +11 -7
- package/skills/softr-sdk/auth/SKILL.md +222 -36
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +178 -0
- package/skills/softr-sdk/charts-and-metrics/references/aggregations.md +76 -0
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/data-mutations/SKILL.md +76 -56
- package/skills/softr-sdk/reading-records/SKILL.md +77 -93
- package/skills/softr-sdk/reading-records/references/field-types.md +41 -48
- package/skills/softr-sdk/reading-records/references/query-builder.md +66 -70
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
- package/dist/core/datasource.d.ts +0 -31
- package/dist/core/datasource.js +0 -226
- package/dist/core/query.d.ts +0 -354
- package/dist/core/query.js +0 -528
- package/dist/core/utils.js +0 -14
- package/dist/types.d.ts +0 -128
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
|
+
import { writeCookies } from "../../cookies.js";
|
|
3
|
+
import { sessionConfig } from "../config.js";
|
|
4
|
+
import { makePlatform } from "../platform.js";
|
|
5
|
+
import { makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
6
|
+
import { authError } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
//#region src/auth/methods/magicLink.ts
|
|
9
|
+
/**
|
|
10
|
+
* Signs a visitor in with the token from a magic link they followed.
|
|
11
|
+
*
|
|
12
|
+
* The token keeps working after it is used, so anyone holding the link can sign
|
|
13
|
+
* in as that visitor. Never log it, and redirect away from the URL that carried
|
|
14
|
+
* it.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
async function signInWithMagicLink(credentials) {
|
|
18
|
+
const result = await makePlatform(sessionConfig().applicationId).signInWithMagicLink(credentials);
|
|
19
|
+
if (!result.ok) {
|
|
20
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
21
|
+
if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
|
|
22
|
+
if (result.status >= 500) panic(`Expected magic-link sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
23
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app signs people in with a magic link.`);
|
|
24
|
+
}
|
|
25
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
26
|
+
const { mfa } = result.data;
|
|
27
|
+
assert(mfa, "Expected a challenge from magic-link sign-in, but got none");
|
|
28
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
data: {
|
|
32
|
+
status: "authenticatorRequired",
|
|
33
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const { session } = result.data;
|
|
38
|
+
assert(session, "Expected a session from magic-link sign-in, but got none");
|
|
39
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
40
|
+
user: session.user,
|
|
41
|
+
permissions: session.permissions
|
|
42
|
+
}, session.jwt)]);
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
data: {
|
|
46
|
+
status: "signedIn",
|
|
47
|
+
user: session.user
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { signInWithMagicLink };
|
|
@@ -5,17 +5,31 @@ import { AuthError, Result } from "./utils.js";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Signs a visitor in with an email and password.
|
|
8
|
+
*
|
|
9
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
10
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
11
|
+
* to send them to next.
|
|
8
12
|
*/
|
|
9
13
|
declare function signIn(credentials: {
|
|
10
14
|
email: string;
|
|
11
15
|
password: string;
|
|
12
|
-
}): Promise<Result<
|
|
16
|
+
}): Promise<Result<{
|
|
17
|
+
status: "signedIn";
|
|
18
|
+
user: User;
|
|
19
|
+
} | {
|
|
20
|
+
status: "authenticatorRequired";
|
|
21
|
+
/**
|
|
22
|
+
* `verify`: they have an authenticator app and owe a code.
|
|
23
|
+
* `setup`: the app requires one and they have none yet.
|
|
24
|
+
* `setupOptional`: same, except they may skip it instead.
|
|
25
|
+
*/
|
|
26
|
+
step: "verify" | "setup" | "setupOptional";
|
|
27
|
+
}, AuthError>>;
|
|
13
28
|
/**
|
|
14
|
-
* Signs the visitor out
|
|
29
|
+
* Signs the visitor out.
|
|
15
30
|
*
|
|
16
|
-
* This
|
|
17
|
-
*
|
|
18
|
-
* before it. There is no per-device sign-out.
|
|
31
|
+
* This ends **every** session that user has in this app, on every device. There
|
|
32
|
+
* is no per-device sign-out.
|
|
19
33
|
*/
|
|
20
34
|
declare function signOut(): Promise<void>;
|
|
21
35
|
//#endregion
|
|
@@ -1,44 +1,65 @@
|
|
|
1
|
-
import { panic, softAssert } from "../../assertions.js";
|
|
1
|
+
import { assert, panic, softAssert } from "../../assertions.js";
|
|
2
2
|
import { writeCookies } from "../../cookies.js";
|
|
3
3
|
import { sessionConfig } from "../config.js";
|
|
4
4
|
import { makePlatform } from "../platform.js";
|
|
5
|
-
import { clearSession, makeSessionDataCookie, makeSessionTokenCookie, readSessionToken } from "../session.js";
|
|
5
|
+
import { clearPendingAuth, clearSession, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readSessionToken } from "../session.js";
|
|
6
6
|
import { authError } from "./utils.js";
|
|
7
7
|
|
|
8
8
|
//#region src/auth/methods/password.ts
|
|
9
9
|
/**
|
|
10
10
|
* Signs a visitor in with an email and password.
|
|
11
|
+
*
|
|
12
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
13
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
14
|
+
* to send them to next.
|
|
11
15
|
*/
|
|
12
16
|
async function signIn(credentials) {
|
|
13
17
|
const result = await makePlatform(sessionConfig().applicationId).tokens(credentials);
|
|
14
18
|
if (!result.ok) {
|
|
15
|
-
if (result.status === 401
|
|
16
|
-
if (result.status
|
|
19
|
+
if (result.status === 401) return authError("invalidCredentials", result.message);
|
|
20
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
21
|
+
if (result.status >= 500) panic(`Expected sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
17
22
|
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-in.`);
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
25
|
+
const { mfa } = result.data;
|
|
26
|
+
assert(mfa, "Expected a challenge from sign-in, but got none");
|
|
27
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
data: {
|
|
31
|
+
status: "authenticatorRequired",
|
|
32
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const { session } = result.data;
|
|
37
|
+
assert(session, "Expected a session from sign-in, but got none");
|
|
38
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
39
|
+
user: session.user,
|
|
40
|
+
permissions: session.permissions
|
|
41
|
+
}, session.jwt)]);
|
|
42
|
+
clearPendingAuth();
|
|
24
43
|
return {
|
|
25
44
|
success: true,
|
|
26
|
-
data:
|
|
45
|
+
data: {
|
|
46
|
+
status: "signedIn",
|
|
47
|
+
user: session.user
|
|
48
|
+
}
|
|
27
49
|
};
|
|
28
50
|
}
|
|
29
51
|
/**
|
|
30
|
-
* Signs the visitor out
|
|
52
|
+
* Signs the visitor out.
|
|
31
53
|
*
|
|
32
|
-
* This
|
|
33
|
-
*
|
|
34
|
-
* before it. There is no per-device sign-out.
|
|
54
|
+
* This ends **every** session that user has in this app, on every device. There
|
|
55
|
+
* is no per-device sign-out.
|
|
35
56
|
*/
|
|
36
57
|
async function signOut() {
|
|
37
58
|
const token = readSessionToken();
|
|
38
59
|
clearSession();
|
|
39
60
|
if (!token) return;
|
|
40
61
|
const result = await makePlatform(sessionConfig().applicationId).signOut(token);
|
|
41
|
-
softAssert(result.ok || result.status < 500, () => `
|
|
62
|
+
softAssert(result.ok || result.status < 500, () => `Expected sign-out to succeed, but got ${result.status}. This browser is signed out, other devices may not be.`);
|
|
42
63
|
}
|
|
43
64
|
|
|
44
65
|
//#endregion
|
|
@@ -13,25 +13,33 @@ declare function signUp(details: {
|
|
|
13
13
|
name?: string;
|
|
14
14
|
}): Promise<Result<null, AuthError>>;
|
|
15
15
|
/**
|
|
16
|
-
* Finishes sign-up
|
|
16
|
+
* Finishes a sign-up with the emailed code, creating the account and signing
|
|
17
|
+
* the visitor in.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* `password` is what the account is created with. Leave it out only for an app
|
|
20
|
+
* that signs people in with an emailed code. An app expecting a password
|
|
21
|
+
* rejects a sign-up without one, and the other way round.
|
|
22
|
+
*
|
|
23
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
24
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
25
|
+
* to send them to next.
|
|
22
26
|
*/
|
|
23
27
|
declare function confirmSignUp(details: {
|
|
24
28
|
email: string;
|
|
25
|
-
/**
|
|
26
|
-
* What `users/new` creates the account with, and unused on a code-only app,
|
|
27
|
-
* which never reaches that request.
|
|
28
|
-
*
|
|
29
|
-
* TODO: temporary in one respect — it is spent a second time on the
|
|
30
|
-
* 400-after-create workaround below, which goes away with the platform fix.
|
|
31
|
-
*/
|
|
32
|
-
password: string;
|
|
33
29
|
code: string;
|
|
34
30
|
name?: string;
|
|
35
|
-
|
|
31
|
+
password?: string;
|
|
32
|
+
}): Promise<Result<{
|
|
33
|
+
status: "signedIn";
|
|
34
|
+
user: User;
|
|
35
|
+
} | {
|
|
36
|
+
status: "authenticatorRequired";
|
|
37
|
+
/**
|
|
38
|
+
* `verify`: they have an authenticator app and owe a code.
|
|
39
|
+
* `setup`: the app requires one and they have none yet.
|
|
40
|
+
* `setupOptional`: same, except they may skip it instead.
|
|
41
|
+
*/
|
|
42
|
+
step: "verify" | "setup" | "setupOptional";
|
|
43
|
+
}, AuthError>>;
|
|
36
44
|
//#endregion
|
|
37
45
|
export { confirmSignUp, signUp };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
2
|
import { writeCookies } from "../../cookies.js";
|
|
3
3
|
import { sessionConfig } from "../config.js";
|
|
4
4
|
import { makePlatform } from "../platform.js";
|
|
5
|
-
import { makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
5
|
+
import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
6
6
|
import { authError } from "./utils.js";
|
|
7
|
-
import { signIn } from "./password.js";
|
|
8
7
|
|
|
9
8
|
//#region src/auth/methods/signup.ts
|
|
10
9
|
/**
|
|
@@ -13,11 +12,13 @@ import { signIn } from "./password.js";
|
|
|
13
12
|
* User only exists once `confirmSignUp` returns the code.
|
|
14
13
|
*/
|
|
15
14
|
async function signUp(details) {
|
|
16
|
-
const result = await makePlatform(sessionConfig().applicationId).
|
|
15
|
+
const result = await makePlatform(sessionConfig().applicationId).sendSignUpCode(details);
|
|
17
16
|
if (!result.ok) {
|
|
18
|
-
if (result.status >= 500) panic(result.message);
|
|
19
17
|
if (result.status === 409) return authError("emailTaken", result.message);
|
|
20
|
-
return authError("
|
|
18
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
19
|
+
if (result.status === 400) return authError("invalidDetails", result.message);
|
|
20
|
+
if (result.status >= 500) panic(`Expected the sign-up code to be emailed, but got ${result.status}: ` + result.message);
|
|
21
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
21
22
|
}
|
|
22
23
|
return {
|
|
23
24
|
success: true,
|
|
@@ -25,63 +26,56 @@ async function signUp(details) {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
* Finishes sign-up
|
|
29
|
+
* Finishes a sign-up with the emailed code, creating the account and signing
|
|
30
|
+
* the visitor in.
|
|
29
31
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* `password` is what the account is created with. Leave it out only for an app
|
|
33
|
+
* that signs people in with an emailed code. An app expecting a password
|
|
34
|
+
* rejects a sign-up without one, and the other way round.
|
|
35
|
+
*
|
|
36
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
37
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
38
|
+
* to send them to next.
|
|
34
39
|
*/
|
|
35
40
|
async function confirmSignUp(details) {
|
|
36
41
|
const platform = makePlatform(sessionConfig().applicationId);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const { password,...rest } = details;
|
|
43
|
+
const result = password === void 0 ? await platform.signUpWithCode(rest) : await platform.signUpWithPassword({
|
|
44
|
+
...rest,
|
|
45
|
+
password
|
|
40
46
|
});
|
|
41
|
-
if (!
|
|
42
|
-
if (
|
|
43
|
-
if (
|
|
44
|
-
return authError("invalidCode",
|
|
47
|
+
if (!result.ok) {
|
|
48
|
+
if (result.status === 409) return authError("emailTaken", result.message);
|
|
49
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
50
|
+
if (result.status === 400 || result.status === 401) return authError("invalidCode", result.message);
|
|
51
|
+
if (result.status >= 500) panic(`Expected sign-up to succeed, but got ${result.status}: ` + result.message);
|
|
52
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const signedIn = await signIn({
|
|
58
|
-
email: details.email,
|
|
59
|
-
password: details.password
|
|
60
|
-
});
|
|
61
|
-
if (!signedIn.success) return authError("invalidCode", created.message);
|
|
62
|
-
return signedIn;
|
|
63
|
-
}
|
|
54
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
55
|
+
const { mfa } = result.data;
|
|
56
|
+
assert(mfa, "Expected a challenge from sign-up, but got none");
|
|
57
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
data: {
|
|
61
|
+
status: "authenticatorRequired",
|
|
62
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
const { session } = result.data;
|
|
67
|
+
assert(session, "Expected a session from sign-up, but got none");
|
|
68
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
69
|
+
user: session.user,
|
|
70
|
+
permissions: session.permissions
|
|
71
|
+
}, session.jwt)]);
|
|
72
|
+
clearPendingAuth();
|
|
71
73
|
return {
|
|
72
74
|
success: true,
|
|
73
|
-
data:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* `name` is dropped when absent rather than sent as `undefined`: the platform's
|
|
78
|
-
* validator runs on whatever key is present, and an explicit null is not the
|
|
79
|
-
* same request as no name at all.
|
|
80
|
-
*/
|
|
81
|
-
function signUpBody(details) {
|
|
82
|
-
return details.name === void 0 ? { email: details.email } : {
|
|
83
|
-
email: details.email,
|
|
84
|
-
name: details.name
|
|
75
|
+
data: {
|
|
76
|
+
status: "signedIn",
|
|
77
|
+
user: session.user
|
|
78
|
+
}
|
|
85
79
|
};
|
|
86
80
|
}
|
|
87
81
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { User } from "../user.js";
|
|
2
|
+
import { AuthError, Result } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
//#region src/auth/methods/social.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Signs a visitor in with a Google token the browser obtained, creating their
|
|
8
|
+
* account the first time.
|
|
9
|
+
*
|
|
10
|
+
* Pass the id token Google Identity Services handed the page. An access token
|
|
11
|
+
* is rejected, and so is a token minted for another app. `fullName` names a
|
|
12
|
+
* first-time visitor and does nothing for one who already has an account.
|
|
13
|
+
*
|
|
14
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
15
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
16
|
+
* to send them to next.
|
|
17
|
+
*/
|
|
18
|
+
declare function signInWithGoogle(credentials: {
|
|
19
|
+
email: string;
|
|
20
|
+
token: string;
|
|
21
|
+
fullName?: string;
|
|
22
|
+
}): Promise<Result<{
|
|
23
|
+
status: "signedIn";
|
|
24
|
+
user: User;
|
|
25
|
+
} | {
|
|
26
|
+
status: "authenticatorRequired";
|
|
27
|
+
/**
|
|
28
|
+
* `verify`: they have an authenticator app and owe a code.
|
|
29
|
+
* `setup`: the app requires one and they have none yet.
|
|
30
|
+
* `setupOptional`: same, except they may skip it instead.
|
|
31
|
+
*/
|
|
32
|
+
step: "verify" | "setup" | "setupOptional";
|
|
33
|
+
}, AuthError>>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { signInWithGoogle };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assert, panic } from "../../assertions.js";
|
|
2
|
+
import { writeCookies } from "../../cookies.js";
|
|
3
|
+
import { sessionConfig } from "../config.js";
|
|
4
|
+
import { makePlatform } from "../platform.js";
|
|
5
|
+
import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
|
|
6
|
+
import { authError } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
//#region src/auth/methods/social.ts
|
|
9
|
+
/**
|
|
10
|
+
* Signs a visitor in with a Google token the browser obtained, creating their
|
|
11
|
+
* account the first time.
|
|
12
|
+
*
|
|
13
|
+
* Pass the id token Google Identity Services handed the page. An access token
|
|
14
|
+
* is rejected, and so is a token minted for another app. `fullName` names a
|
|
15
|
+
* first-time visitor and does nothing for one who already has an account.
|
|
16
|
+
*
|
|
17
|
+
* A `signedIn` result means the visitor is signed in. An app that also asks for
|
|
18
|
+
* an authenticator app answers `authenticatorRequired` instead, naming the step
|
|
19
|
+
* to send them to next.
|
|
20
|
+
*/
|
|
21
|
+
async function signInWithGoogle(credentials) {
|
|
22
|
+
const result = await makePlatform(sessionConfig().applicationId).signInWithGoogle(credentials);
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
if (result.status === 429) return authError("tooManyAttempts", result.message);
|
|
25
|
+
if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
|
|
26
|
+
if (result.status >= 500) panic(`Expected Google sign-in to succeed, but got ${result.status}: ` + result.message);
|
|
27
|
+
throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows Google sign-in.`);
|
|
28
|
+
}
|
|
29
|
+
if (result.data.status === "MFA_REQUIRED") {
|
|
30
|
+
const { mfa } = result.data;
|
|
31
|
+
assert(mfa, "Expected a challenge from Google sign-in, but got none");
|
|
32
|
+
writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: {
|
|
36
|
+
status: "authenticatorRequired",
|
|
37
|
+
step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const { session } = result.data;
|
|
42
|
+
assert(session, "Expected a session from Google sign-in, but got none");
|
|
43
|
+
writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
|
|
44
|
+
user: session.user,
|
|
45
|
+
permissions: session.permissions
|
|
46
|
+
}, session.jwt)]);
|
|
47
|
+
clearPendingAuth();
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
data: {
|
|
51
|
+
status: "signedIn",
|
|
52
|
+
user: session.user
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { signInWithGoogle };
|
|
@@ -10,6 +10,6 @@ type AuthError = {
|
|
|
10
10
|
code: AuthErrorCode;
|
|
11
11
|
message: string;
|
|
12
12
|
};
|
|
13
|
-
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode";
|
|
13
|
+
type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated";
|
|
14
14
|
//#endregion
|
|
15
15
|
export { AuthError, Result };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DatasourcePermissions } from "../types.js";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
|
|
4
3
|
//#region src/auth/permissions.d.ts
|
|
@@ -9,9 +8,15 @@ import { z } from "zod";
|
|
|
9
8
|
* Rendering hints, not enforcement: every data request is authorized again
|
|
10
9
|
* server-side, and these may go stale between the two.
|
|
11
10
|
*/
|
|
11
|
+
type DatasourcePermissions = {
|
|
12
|
+
read: boolean;
|
|
13
|
+
create: boolean;
|
|
14
|
+
update: boolean;
|
|
15
|
+
delete: boolean;
|
|
16
|
+
};
|
|
12
17
|
type Permissions = {
|
|
13
18
|
/** Keyed by datasource id. */
|
|
14
19
|
datasources: Record<string, DatasourcePermissions>;
|
|
15
20
|
};
|
|
16
21
|
//#endregion
|
|
17
|
-
export { Permissions };
|
|
22
|
+
export { DatasourcePermissions, Permissions };
|