@slashid/jump-page 0.0.6 → 0.0.7-beta.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.
@@ -1,32 +1,32 @@
1
1
 
2
- > @slashid/jump-page@0.0.6 build /home/runner/work/javascript/javascript/apps/jump-page
2
+ > @slashid/jump-page@0.0.6 build /Users/ivankovic/projects/slashid/javascript/apps/jump-page
3
3
  > astro build
4
4
 
5
- 12:55:55 PM [content] No content directory found. Skipping type generation.
6
- 12:55:55 PM [build] output target: static
7
- 12:55:55 PM [build] Collecting build info...
8
- 12:55:55 PM [build] Completed in 116ms.
9
- 12:55:55 PM [build] Building static entrypoints...
10
- 12:55:58 PM [build] Completed in 3.16s.
5
+ 05:00:06 PM [content] No content directory found. Skipping type generation.
6
+ 05:00:06 PM [build] output target: static
7
+ 05:00:06 PM [build] Collecting build info...
8
+ 05:00:06 PM [build] Completed in 111ms.
9
+ 05:00:06 PM [build] Building static entrypoints...
10
+ 05:00:07 PM [build] Completed in 1.68s.
11
11
 
12
12
  building client
13
- vite v4.5.0 building for production...
13
+ vite v4.5.0 building for production...
14
14
  transforming...
15
- ✓ 377 modules transformed.
15
+ 384 modules transformed.
16
16
  rendering chunks...
17
17
  computing gzip size...
18
- dist/_astro/index.e24832a0.css  31.53 kB │ gzip: 5.44 kB
19
- dist/_astro/index.03be2d59.js  6.83 kB │ gzip: 2.72 kB
20
- dist/_astro/client.a5ffbec0.js 135.34 kB │ gzip: 43.73 kB
21
- dist/_astro/app.16dd2e70.js 166.26 kB │ gzip: 47.25 kB
22
- ✓ built in 3.46s
23
- Completed in 3.46s.
18
+ dist/_astro/index.cc62d81f.css 31.75 kB │ gzip: 5.49 kB
19
+ dist/_astro/index.03be2d59.js 6.83 kB │ gzip: 2.72 kB
20
+ dist/_astro/client.a5ffbec0.js 135.34 kB │ gzip: 43.73 kB
21
+ dist/_astro/app.51fc6a0f.js 203.42 kB │ gzip: 56.61 kB
22
+ ✓ built in 1.94s
23
+ Completed in 1.95s.
24
24
 
25
25
 
26
26
  generating static routes
27
27
  ▶ src/pages/index.astro
28
- └─ /index.html (+85ms)
29
- Completed in 96ms.
28
+ └─ /index.html (+122ms)
29
+ Completed in 132ms.
30
30
 
31
- 12:56:02 PM [build] 1 page(s) built in 6.85s
32
- 12:56:02 PM [build] Complete!
31
+ 05:00:09 PM [build] 1 page(s) built in 3.88s
32
+ 05:00:09 PM [build] Complete!
package/astro.config.mjs CHANGED
@@ -1,12 +1,25 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
1
3
  import { defineConfig } from "astro/config";
2
4
 
3
5
  import react from "@astrojs/react";
4
6
  import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
5
7
 
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
6
11
  // https://astro.build/config
7
12
  export default defineConfig({
8
13
  integrations: [react()],
9
14
  vite: {
10
15
  plugins: [vanillaExtractPlugin()],
16
+ resolve: {
17
+ alias: {
18
+ "@slashid/react-primitives": path.resolve(
19
+ __dirname,
20
+ "../../packages/react-primitives/src/main.ts"
21
+ ),
22
+ },
23
+ },
11
24
  },
12
25
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slashid/jump-page",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.7-beta.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "restricted"
@@ -9,29 +9,28 @@
9
9
  "bin": {
10
10
  "sid-jump-cli": "./cli.js"
11
11
  },
12
+ "scripts": {
13
+ "dev": "astro dev",
14
+ "start": "astro dev",
15
+ "test": "astro check",
16
+ "build": "astro build",
17
+ "preview": "astro preview",
18
+ "astro": "astro"
19
+ },
12
20
  "dependencies": {
13
21
  "@astrojs/check": "^0.3.1",
14
22
  "@astrojs/react": "^3.0.5",
15
23
  "@sentry/react": "^7.81.1",
16
- "@slashid/slashid": "3.16.0",
24
+ "@slashid/slashid": "3.17.4-beta.3",
17
25
  "@types/react": "^18.0.21",
18
26
  "@types/react-dom": "^18.0.6",
19
27
  "astro": "^3.5.5",
20
28
  "commander": "^11.1.0",
21
29
  "react": "^18.0.0",
22
- "react-dom": "^18.0.0",
23
- "@slashid/react-primitives": "0.1.4"
30
+ "react-dom": "^18.0.0"
24
31
  },
25
32
  "devDependencies": {
26
33
  "@vanilla-extract/css": "^1.9.2",
27
34
  "@vanilla-extract/vite-plugin": "^3.9.2"
28
- },
29
- "scripts": {
30
- "dev": "astro dev",
31
- "start": "astro dev",
32
- "test": "astro check",
33
- "build": "astro build",
34
- "preview": "astro preview",
35
- "astro": "astro"
36
35
  }
37
- }
36
+ }
@@ -26,6 +26,7 @@ export const header = style({
26
26
  export const footer = style({
27
27
  width: "100%",
28
28
  textAlign: "center",
29
+ marginTop: theme.space[8],
29
30
  });
30
31
 
31
32
  export const logo = style({
@@ -10,9 +10,15 @@ import type { ChallengeListInner } from "@slashid/slashid";
10
10
  import { Success } from "./flow.success";
11
11
  import { ensureError } from "./flow.domain";
12
12
 
13
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
+ function isPasswordRecoveryFlow(challenges: Challenges): boolean {
14
+ return challenges.some((challenge) => challenge.type === "password_reset");
15
+ }
16
+
14
17
  function getFlowTypeFromChallenges(challenges: Challenges): FlowType {
15
- // TODO treat passwords as a special case
18
+ if (isPasswordRecoveryFlow(challenges)) {
19
+ return "password-recovery";
20
+ }
21
+
16
22
  return "catch-all";
17
23
  }
18
24
 
@@ -77,7 +83,11 @@ export function Flow() {
77
83
  {appState === "ready" && (
78
84
  <>
79
85
  {flowState.state === "progress" && (
80
- <Progress onSuccess={handleSuccess} onError={handleError} />
86
+ <Progress
87
+ onSuccess={handleSuccess}
88
+ onError={handleError}
89
+ flowType={flowState.flowType}
90
+ />
81
91
  )}
82
92
  {flowState.state === "no-challenges" && <Error type="warning" />}
83
93
  {flowState.state === "success" && <Success />}
@@ -0,0 +1,20 @@
1
+ import { publicVariables, theme } from "@slashid/react-primitives";
2
+ import { style } from "@vanilla-extract/css";
3
+
4
+ export const formInputs = style({
5
+ margin: "24px 0",
6
+ });
7
+
8
+ export const passwordRecoveryPrompt = style({
9
+ display: "flex",
10
+ alignItems: "baseline",
11
+ marginTop: "8px",
12
+ });
13
+
14
+ export const errorMessage = style({
15
+ display: "block",
16
+ marginTop: theme.space[2],
17
+ color: publicVariables.color.error,
18
+ fontWeight: "600",
19
+ lineHeight: "122%",
20
+ });
@@ -1,9 +1,12 @@
1
- import { Stack } from "@slashid/react-primitives";
1
+ import { useEffect } from "react";
2
2
  import * as Sentry from "@sentry/react";
3
+ import { Stack } from "@slashid/react-primitives";
4
+
3
5
  import { Loader } from "./flow.loader";
6
+ import type { FlowType } from "./flow.types";
4
7
  import { Text } from "../text";
5
- import { useEffect } from "react";
6
8
  import { useAppContext } from "../app/app.context";
9
+ import { Recover } from "./flow.recover";
7
10
 
8
11
  type SlashIDErrorMessage = { message: string };
9
12
  class SlashIDError extends Error {
@@ -51,12 +54,14 @@ function ensureError(value: unknown): Error {
51
54
  }
52
55
 
53
56
  export type Props = {
57
+ flowType: FlowType;
54
58
  onSuccess: () => void;
55
59
  onError: ({ error }: { error: Error }) => void;
56
60
  };
57
61
 
58
- export function Progress({ onSuccess, onError }: Props) {
62
+ export function Progress({ onSuccess, onError, flowType }: Props) {
59
63
  const { sdk } = useAppContext();
64
+
60
65
  useEffect(() => {
61
66
  if (!sdk) return;
62
67
 
@@ -71,8 +76,28 @@ export function Progress({ onSuccess, onError }: Props) {
71
76
  }
72
77
  }
73
78
 
79
+ function handlePasswordResetEvent() {
80
+ console.log("Password reset - input ready");
81
+ }
82
+
83
+ if (flowType === "password-recovery") {
84
+ console.log("Subscribed");
85
+ sdk.subscribe("passwordResetReady", handlePasswordResetEvent);
86
+ }
87
+
74
88
  authenticate();
75
- }, [onError, onSuccess, sdk]);
89
+
90
+ return () => {
91
+ if (flowType === "password-recovery") {
92
+ console.log("Cleaned up");
93
+ sdk.unsubscribe("passwordResetReady", handlePasswordResetEvent);
94
+ }
95
+ };
96
+ }, [flowType, onError, onSuccess, sdk]);
97
+
98
+ if (flowType === "password-recovery") {
99
+ return <Recover />;
100
+ }
76
101
 
77
102
  return (
78
103
  <>
@@ -0,0 +1,121 @@
1
+ import type { InvalidPasswordSubmittedEvent } from "@slashid/slashid";
2
+ import { useEffect, useState } from "react";
3
+ import {
4
+ Input,
5
+ Stack,
6
+ Text,
7
+ Button,
8
+ sprinkles,
9
+ } from "@slashid/react-primitives";
10
+
11
+ import * as styles from "./flow.css";
12
+ import { useI18n } from "../text/use-i18n";
13
+ import { useAppContext } from "../app/app.context";
14
+ import type { TranslationKeys } from "../../domain/i18n";
15
+
16
+ function getValidationI18nKey(
17
+ errorEvent: InvalidPasswordSubmittedEvent
18
+ ): TranslationKeys {
19
+ const textKey = `authenticating.setPassword.validation.${errorEvent.failedRules[0].name}`;
20
+
21
+ // prefer the first error
22
+ return textKey as TranslationKeys;
23
+ }
24
+
25
+ function ErrorMessage({ message }: { message: string }) {
26
+ return <span className={styles.errorMessage}>{message}</span>;
27
+ }
28
+
29
+ export function Recover() {
30
+ const i18n = useI18n();
31
+ const { sdk } = useAppContext();
32
+ const [password, setPassword] = useState("");
33
+ const [passwordConfirm, setPasswordConfirm] = useState("");
34
+ const [error, setError] = useState<string | null>(null);
35
+
36
+ useEffect(() => {
37
+ const onInvalidPassword = (
38
+ invalidPasswordEvent: InvalidPasswordSubmittedEvent
39
+ ) => {
40
+ console.log("onInvalidPassword", invalidPasswordEvent);
41
+ setError(i18n(getValidationI18nKey(invalidPasswordEvent)));
42
+ };
43
+
44
+ sdk?.subscribe("invalidPasswordSubmitted", onInvalidPassword);
45
+
46
+ return () => {
47
+ sdk?.unsubscribe("invalidPasswordSubmitted", onInvalidPassword);
48
+ };
49
+ }, [i18n, sdk, setError]);
50
+
51
+ const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
52
+ event.preventDefault();
53
+
54
+ if (!sdk) {
55
+ return;
56
+ }
57
+
58
+ sdk.publish("passwordSubmitted", password);
59
+ };
60
+
61
+ const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
62
+ setPassword(event.target.value);
63
+ };
64
+
65
+ const handleConfirmPasswordChange = (
66
+ event: React.ChangeEvent<HTMLInputElement>
67
+ ) => {
68
+ setPasswordConfirm(event.target.value);
69
+ };
70
+
71
+ return (
72
+ <>
73
+ <Stack space="0.25">
74
+ <Text
75
+ as="h1"
76
+ variant={{ size: "2xl-title", weight: "bold" }}
77
+ t="recover.password.title"
78
+ />
79
+ <Text
80
+ variant={{ color: "contrast", weight: "semibold" }}
81
+ as="h2"
82
+ t="recover.password.details"
83
+ />
84
+ </Stack>
85
+ <form onSubmit={handleSubmit}>
86
+ <div className={styles.formInputs}>
87
+ {/* TODO add an error state to the input (change border color) */}
88
+ <Input
89
+ id="password-input"
90
+ label={i18n("recover.password.input.password.label")}
91
+ placeholder={i18n("recover.password.input.placeholder")}
92
+ name="password"
93
+ type="password"
94
+ value={password ?? ""}
95
+ onChange={handlePasswordChange}
96
+ />
97
+ <Input
98
+ id="password-input-confirm"
99
+ label={i18n("recover.password.input.confirmPassword.label")}
100
+ placeholder={i18n("recover.password.input.placeholder")}
101
+ name="passwordConfirm"
102
+ type="password"
103
+ value={passwordConfirm ?? ""}
104
+ onChange={handleConfirmPasswordChange}
105
+ className={sprinkles({ marginTop: "4" })}
106
+ />
107
+ {error && <ErrorMessage message={error} />}
108
+ </div>
109
+
110
+ <Button
111
+ type="submit"
112
+ variant="primary"
113
+ testId="sid-form-initial-submit-button"
114
+ disabled={!password || password !== passwordConfirm}
115
+ >
116
+ {i18n("recover.password.submit")}
117
+ </Button>
118
+ </form>
119
+ </>
120
+ );
121
+ }
@@ -20,6 +20,7 @@ const CheckIcon = () => (
20
20
  </Circle>
21
21
  );
22
22
 
23
+ // TODO this view always tells you are logged in, even in cases when you just verify the handle and not have a token
23
24
  export function Success() {
24
25
  return (
25
26
  <article data-testid="sid-form-success-state">
@@ -40,7 +40,7 @@ export type State =
40
40
  | SuccessState
41
41
  | ErrorState;
42
42
 
43
- export type FlowType = "catch-all";
43
+ export type FlowType = "catch-all" | "password-recovery";
44
44
 
45
45
  export type ChallengesInURL = Awaited<
46
46
  ReturnType<SlashID["getChallengesFromURL"]>
@@ -0,0 +1,13 @@
1
+ import { useMemo } from "react";
2
+ import { createI18n } from "../../domain/i18n";
3
+ import { useAppContext } from "../app/app.context";
4
+
5
+ export function useI18n() {
6
+ const { language } = useAppContext();
7
+
8
+ const i18n = useMemo(() => {
9
+ return createI18n(language);
10
+ }, [language]);
11
+
12
+ return i18n;
13
+ }
package/src/config.ts CHANGED
@@ -1,7 +1,13 @@
1
- export const config = {
1
+ // TODO this does not work with .env files
2
+ /* export const config = {
2
3
  sdkURL:
3
4
  import.meta.env.PUBLIC_SID_SDK_URL ||
4
5
  "https://cdn.sandbox.slashid.com/sdk.html",
5
6
  baseURL:
6
7
  import.meta.env.PUBLIC_SID_API_URL || "https://api.sandbox.slashid.com",
8
+ }; */
9
+
10
+ export const config = {
11
+ baseURL: "https://slashid.local",
12
+ sdkURL: "https://jump.slashid.local/sdk.html",
7
13
  };
@@ -18,6 +18,29 @@ export const defaultStrings = {
18
18
  "footer.text": "Top-tier security by SlashID",
19
19
  "initial.title": "Logging you in...",
20
20
  "initial.details": "Please follow the on-screen instructions.",
21
+ "recover.password.title": "Reset password",
22
+ "recover.password.details": "Enter your new password.",
23
+ "recover.password.input.password.label": "Password",
24
+ "recover.password.input.confirmPassword.label": "Confirm password",
25
+ "recover.password.input.placeholder": "Type your new password",
26
+ "recover.password.submit": "Reset password",
27
+ "recover.password.validation.length": "Password is too short",
28
+ "recover.password.validation.password_variants": "Contains word 'password'",
29
+ "recover.password.validation.admin_variants": "Contains word 'admin'",
30
+ "recover.password.validation.user_variants": "Contains word 'user'",
31
+ "recover.password.validation.alphanumeric_sequences_1":
32
+ "Sequence of alphanumeric characters",
33
+ "recover.password.validation.alphanumeric_sequences_2":
34
+ "Sequence of alphanumeric characters",
35
+ "recover.password.validation.numeric_sequences_ascending":
36
+ "Sequence of alphanumeric characters",
37
+ "recover.password.validation.numeric_subsequences_ascending":
38
+ "Sequence of alphanumeric characters",
39
+ "recover.password.validation.numeric_sequences_descending":
40
+ "Sequence of alphanumeric characters",
41
+ "recover.password.validation.numeric_subsequences_descending":
42
+ "Sequence of alphanumeric characters",
43
+ "recover.password.validation.common_password_xkcd": "Common password",
21
44
  "success.title": "Thank you, you're signed in!",
22
45
  "success.details": "You can now close this page.",
23
46
  "error.title": "Something went wrong...",
@@ -31,6 +54,7 @@ export type Translations = {
31
54
  };
32
55
  };
33
56
 
57
+ // TODO add translations for password validation errors
34
58
  export const I18N: Translations = {
35
59
  en: defaultStrings,
36
60
  ja: {
@@ -39,6 +63,43 @@ export const I18N: Translations = {
39
63
  "initial.details": "処理完了後、画面は自動で切り替わります。",
40
64
  "success.title": "認証が完了しました!",
41
65
  "success.details": "このページを閉じて、元のページへ戻ってください。",
66
+ "recover.password.title": defaultStrings["recover.password.title"],
67
+ "recover.password.details": defaultStrings["recover.password.details"],
68
+ "recover.password.input.password.label":
69
+ defaultStrings["recover.password.input.password.label"],
70
+ "recover.password.input.confirmPassword.label":
71
+ defaultStrings["recover.password.input.confirmPassword.label"],
72
+ "recover.password.input.placeholder":
73
+ defaultStrings["recover.password.input.placeholder"],
74
+ "recover.password.submit": defaultStrings["recover.password.submit"],
75
+ "recover.password.validation.length":
76
+ defaultStrings["recover.password.validation.length"],
77
+ "recover.password.validation.password_variants":
78
+ defaultStrings["recover.password.validation.password_variants"],
79
+ "recover.password.validation.admin_variants":
80
+ defaultStrings["recover.password.validation.admin_variants"],
81
+ "recover.password.validation.user_variants":
82
+ defaultStrings["recover.password.validation.user_variants"],
83
+ "recover.password.validation.alphanumeric_sequences_1":
84
+ defaultStrings["recover.password.validation.alphanumeric_sequences_1"],
85
+ "recover.password.validation.alphanumeric_sequences_2":
86
+ defaultStrings["recover.password.validation.alphanumeric_sequences_2"],
87
+ "recover.password.validation.numeric_sequences_ascending":
88
+ defaultStrings["recover.password.validation.numeric_sequences_ascending"],
89
+ "recover.password.validation.numeric_subsequences_ascending":
90
+ defaultStrings[
91
+ "recover.password.validation.numeric_subsequences_ascending"
92
+ ],
93
+ "recover.password.validation.numeric_sequences_descending":
94
+ defaultStrings[
95
+ "recover.password.validation.numeric_sequences_descending"
96
+ ],
97
+ "recover.password.validation.numeric_subsequences_descending":
98
+ defaultStrings[
99
+ "recover.password.validation.numeric_subsequences_descending"
100
+ ],
101
+ "recover.password.validation.common_password_xkcd":
102
+ defaultStrings["recover.password.validation.common_password_xkcd"],
42
103
  "error.title": defaultStrings["error.title"],
43
104
  "error.detail": "再度ログインをしてください。",
44
105
  },
package/tsconfig.json CHANGED
@@ -2,6 +2,9 @@
2
2
  "extends": "astro/tsconfigs/strict",
3
3
  "compilerOptions": {
4
4
  "jsx": "react-jsx",
5
- "jsxImportSource": "react"
5
+ "jsxImportSource": "react",
6
+ "paths": {
7
+ "@slashid/react-primitives": ["../../packages/react-primitives/src/main.ts"]
8
+ }
6
9
  }
7
- }
10
+ }