@canmingir/link 1.2.11 → 1.2.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link",
3
- "version": "1.2.11",
3
+ "version": "1.2.12",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -21,6 +21,7 @@
21
21
  "cypress": "cypress open --browser chrome"
22
22
  },
23
23
  "dependencies": {
24
+ "@aws-sdk/client-cognito-identity-provider": "^3.968.0",
24
25
  "@chatscope/chat-ui-kit-react": "^1.10.1",
25
26
  "@emoji-mart/data": "^1.1.2",
26
27
  "@emoji-mart/react": "^1.1.1",
@@ -101,7 +102,7 @@
101
102
  "playwright": "^1.55.1",
102
103
  "prettier": "^2.8.8",
103
104
  "storybook": "^9.1.8",
104
- "vite": "^7.1.8",
105
+ "vite": "^7.3.1",
105
106
  "vite-plugin-checker": "^0.9.0",
106
107
  "vite-plugin-svgr": "^4.2.0",
107
108
  "vitest": "^3.2.4"
@@ -10,6 +10,12 @@ export const ConfigSchema = Joi.object({
10
10
  host: Joi.string().uri().required(),
11
11
  path: Joi.string().required(),
12
12
  }).optional(),
13
+ credentials: Joi.object({
14
+ provider: Joi.string().valid("DEMO", "COGNITO").required(),
15
+ region: Joi.string().optional(),
16
+ userPoolId: Joi.string().optional(),
17
+ clientId: Joi.string().optional(),
18
+ }).optional(),
13
19
  project: Joi.object({
14
20
  nucleoid: Joi.object().optional(),
15
21
  github: Joi.object({
package/src/http/index.js CHANGED
@@ -86,27 +86,38 @@ export const fetcher = (url) => instance.get(url).then((res) => res.data);
86
86
  const refreshAuthLogic = async (failedRequest) => {
87
87
  try {
88
88
  const { appId } = config();
89
-
90
89
  const projectId = storage.get("projectId");
91
-
92
90
  const identityProvider = storage.get("link", "identityProvider");
93
91
 
94
- const { data } = await oauth.post("/oauth", {
95
- refreshToken: storage.get("link", "refreshToken"),
96
- appId,
97
- projectId,
98
- identityProvider,
99
- });
92
+ const isDemo = identityProvider?.toUpperCase() === "DEMO";
93
+
94
+ const { data } = isDemo
95
+ ? await oauth.post("/oauth/demo", {
96
+ appId,
97
+ projectId,
98
+ username: "admin",
99
+ password: "admin",
100
+ })
101
+ : await oauth.post("/oauth", {
102
+ refreshToken: storage.get("link", "refreshToken"),
103
+ appId,
104
+ projectId,
105
+ identityProvider,
106
+ });
100
107
 
101
- const accessToken = data.accessToken;
108
+ const { accessToken, refreshToken } = data;
102
109
 
103
110
  failedRequest.response.config.headers["Authorization"] =
104
111
  "Bearer " + accessToken;
105
112
 
106
113
  storage.set("link", "accessToken", accessToken);
114
+ if (refreshToken) {
115
+ storage.set("link", "refreshToken", refreshToken);
116
+ }
117
+
107
118
  return Promise.resolve();
108
119
  } catch (error) {
109
- const { name, base } = config();
120
+ const { base } = config();
110
121
 
111
122
  storage.remove("link", "accessToken");
112
123
  storage.remove("link", "refreshToken");
@@ -4,6 +4,7 @@ import Logo from "../../components/logo";
4
4
  import { Outlet } from "react-router";
5
5
  import React from "react";
6
6
  import Stack from "@mui/material/Stack";
7
+ import { alpha } from "@mui/material/styles";
7
8
  import { useResponsive } from "../../hooks/use-responsive";
8
9
 
9
10
  // ----------------------------------------------------------------------
@@ -16,28 +17,37 @@ export default function AuthModernLayout({ image }) {
16
17
  sx={{
17
18
  width: 1,
18
19
  mx: "auto",
19
- maxWidth: 480,
20
- px: { xs: 2, md: 8 },
20
+ maxWidth: 600,
21
+ px: { xs: 3, md: 10 },
22
+ py: { xs: 4, md: 0 },
21
23
  height: "100vh",
22
24
  justifyContent: "center",
23
25
  alignItems: "center",
24
26
  }}
25
27
  >
26
28
  <Logo
27
- maxSize={200}
29
+ maxSize={140}
28
30
  sx={{
29
- mt: { xs: 2, md: 8 },
30
- mb: { xs: 10, md: 8 },
31
+ mb: { xs: 1, md: 2 },
31
32
  }}
32
33
  />
33
34
 
34
35
  <Card
35
36
  sx={{
36
- py: { xs: 5, md: 0 },
37
- px: { xs: 3, md: 0 },
38
- boxShadow: { md: "none" },
37
+ width: 1,
38
+ py: { xs: 6, md: 8 },
39
+ px: { xs: 4, md: 6 },
40
+ boxShadow: {
41
+ xs: (theme) =>
42
+ `0 0 2px ${alpha(
43
+ theme.palette.grey[500],
44
+ 0.16
45
+ )}, 0 12px 24px -4px ${alpha(theme.palette.grey[500], 0.12)}`,
46
+ md: "none",
47
+ },
39
48
  overflow: { md: "unset" },
40
- bgcolor: { md: "background.default" },
49
+ bgcolor: { md: "transparent" },
50
+ borderRadius: 2,
41
51
  }}
42
52
  >
43
53
  <Outlet />
@@ -46,7 +56,12 @@ export default function AuthModernLayout({ image }) {
46
56
  );
47
57
 
48
58
  const renderSection = (
49
- <Stack flexGrow={1} sx={{ position: "relative" }}>
59
+ <Stack
60
+ flexGrow={1}
61
+ sx={{
62
+ position: "relative",
63
+ }}
64
+ >
50
65
  <Box
51
66
  component="img"
52
67
  alt="auth"
@@ -59,6 +74,7 @@ export default function AuthModernLayout({ image }) {
59
74
  position: "absolute",
60
75
  width: "calc(60% - 32px)",
61
76
  height: "calc(60% - 32px)",
77
+ borderRadius: 3,
62
78
  }}
63
79
  />
64
80
  </Stack>
@@ -94,8 +94,23 @@ function ProjectBar() {
94
94
  const refreshToken = storage.get("link", "refreshToken");
95
95
  const identityProvider = storage.get("link", "identityProvider");
96
96
 
97
- oauth
98
- .post("/oauth", { appId, refreshToken, projectId, identityProvider })
97
+ const isDemo = identityProvider?.toUpperCase() === "DEMO";
98
+
99
+ const request = isDemo
100
+ ? oauth.post("/oauth/demo", {
101
+ appId,
102
+ projectId,
103
+ username: "admin",
104
+ password: "admin",
105
+ })
106
+ : oauth.post("/oauth", {
107
+ appId,
108
+ refreshToken,
109
+ projectId,
110
+ identityProvider,
111
+ });
112
+
113
+ request
99
114
  .then(({ data }) => {
100
115
  const { refreshToken, accessToken } = data;
101
116
  storage.set("link", "accessToken", accessToken);
@@ -104,9 +119,7 @@ function ProjectBar() {
104
119
  })
105
120
  .finally(() => {
106
121
  setSelectedProject(project);
107
-
108
122
  publish("PROJECT_SELECTED", { projectId });
109
-
110
123
  search.onFalse();
111
124
  setSearchQuery("");
112
125
  });
@@ -1,3 +1,5 @@
1
+ import CognitoLogin from "../widgets/Login/CognitoLogin";
2
+ import DemoLogin from "../widgets/Login/DemoLogin";
1
3
  import LoginForm from "../widgets/LoginForm/LoginForm";
2
4
  import Page from "../layouts/Page";
3
5
  import React from "react";
@@ -6,7 +8,7 @@ import { storage } from "@nucleoidjs/webstorage";
6
8
  import { useEffect } from "react";
7
9
  import { useNavigate } from "react-router-dom";
8
10
  function LoginPage() {
9
- const { name, template } = config();
11
+ const { name, template, credentials } = config();
10
12
  const formColor = "#a8a9ad";
11
13
  const navigate = useNavigate();
12
14
 
@@ -28,6 +30,18 @@ function LoginPage() {
28
30
  // eslint-disable-next-line react-hooks/exhaustive-deps
29
31
  }, [navigate]);
30
32
 
33
+ if (credentials?.provider === "COGNITO") {
34
+ return <CognitoLogin />;
35
+ }
36
+
37
+ if (credentials?.provider === "DEMO") {
38
+ return (
39
+ <Page title={`Sign in to ${name}`}>
40
+ <DemoLogin />
41
+ </Page>
42
+ );
43
+ }
44
+
31
45
  return (
32
46
  <Page title={`Sign in to ${name}`}>
33
47
  <LoginForm icon={template.login.icon} name={name} formColor={formColor} />
@@ -0,0 +1,45 @@
1
+ import { loginWithCognito } from "./cognitoAuth";
2
+ import { storage } from "@nucleoidjs/webstorage";
3
+ import { useState } from "react";
4
+
5
+ import { Button, Stack, TextField, Typography } from "@mui/material";
6
+
7
+ export default function CognitoLogin() {
8
+ const [username, setUsername] = useState("");
9
+ const [password, setPassword] = useState("");
10
+
11
+ const handleLogin = async () => {
12
+ try {
13
+ const tokens = await loginWithCognito(username, password);
14
+ console.log("Login successful, tokens:", tokens);
15
+ storage.set("link", "accessToken", tokens.AccessToken);
16
+ storage.set("link", "refreshToken", tokens.RefreshToken);
17
+ window.location.href = "/";
18
+ } catch (e) {
19
+ console.error("Login error:", e);
20
+ alert(`Login failed: ${e.message || e}`);
21
+ }
22
+ };
23
+
24
+ return (
25
+ <>
26
+ <Typography variant="h4">Login</Typography>
27
+ <Stack spacing={2}>
28
+ <TextField
29
+ label="Username or Email"
30
+ value={username}
31
+ onChange={(e) => setUsername(e.target.value)}
32
+ />
33
+ <TextField
34
+ type="password"
35
+ label="Password"
36
+ value={password}
37
+ onChange={(e) => setPassword(e.target.value)}
38
+ />
39
+ <Button variant="contained" onClick={handleLogin}>
40
+ Login
41
+ </Button>
42
+ </Stack>
43
+ </>
44
+ );
45
+ }
@@ -0,0 +1,178 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ IconButton,
5
+ InputAdornment,
6
+ Stack,
7
+ TextField,
8
+ Typography,
9
+ alpha,
10
+ } from "@mui/material";
11
+ import {
12
+ LockOutlined,
13
+ PersonOutline,
14
+ Visibility,
15
+ VisibilityOff,
16
+ } from "@mui/icons-material";
17
+ import React, { useState } from "react";
18
+
19
+ import config from "../../config/config";
20
+ import { storage } from "@nucleoidjs/webstorage";
21
+ import { useNavigate } from "react-router-dom";
22
+
23
+ export default function DemoLogin() {
24
+ const [username, setUsername] = useState("");
25
+ const [password, setPassword] = useState("");
26
+ const [showPassword, setShowPassword] = useState(false);
27
+ const navigate = useNavigate();
28
+
29
+ const { appId } = config();
30
+
31
+ async function handleLogin() {
32
+ const res = await fetch("/api/oauth/demo", {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({
36
+ appId: appId,
37
+ projectId: "cb16e069-6214-47f1-9922-1f7fe7629525",
38
+ username,
39
+ password,
40
+ }),
41
+ });
42
+
43
+ if (!res.ok) throw new Error("Demo login failed");
44
+
45
+ const data = await res.json();
46
+
47
+ storage.set("link", "accessToken", data.accessToken);
48
+ storage.set("link", "refreshToken", data.refreshToken);
49
+ storage.set("link", "identityProvider", "Demo");
50
+
51
+ navigate("/");
52
+ }
53
+
54
+ return (
55
+ <Stack spacing={2.5}>
56
+ <Stack spacing={2}>
57
+ <TextField
58
+ label="Username"
59
+ value={username}
60
+ onChange={(e) => setUsername(e.target.value)}
61
+ fullWidth
62
+ InputProps={{
63
+ startAdornment: (
64
+ <InputAdornment position="start">
65
+ <PersonOutline sx={{ color: "text.secondary", fontSize: 22 }} />
66
+ </InputAdornment>
67
+ ),
68
+ }}
69
+ sx={{
70
+ "& .MuiOutlinedInput-root": {
71
+ fontSize: "1rem",
72
+ "& input": {
73
+ py: 1.5,
74
+ },
75
+ "&:hover fieldset": {
76
+ borderColor: "primary.main",
77
+ },
78
+ },
79
+ }}
80
+ />
81
+
82
+ <TextField
83
+ label="Password"
84
+ type={showPassword ? "text" : "password"}
85
+ value={password}
86
+ onChange={(e) => setPassword(e.target.value)}
87
+ fullWidth
88
+ InputProps={{
89
+ startAdornment: (
90
+ <InputAdornment position="start">
91
+ <LockOutlined sx={{ color: "text.secondary", fontSize: 22 }} />
92
+ </InputAdornment>
93
+ ),
94
+ endAdornment: (
95
+ <InputAdornment position="end">
96
+ <IconButton
97
+ onClick={() => setShowPassword(!showPassword)}
98
+ edge="end"
99
+ size="small"
100
+ tabIndex={-1}
101
+ >
102
+ {showPassword ? <VisibilityOff /> : <Visibility />}
103
+ </IconButton>
104
+ </InputAdornment>
105
+ ),
106
+ }}
107
+ sx={{
108
+ "& .MuiOutlinedInput-root": {
109
+ fontSize: "1rem",
110
+ "& input": {
111
+ py: 1.5,
112
+ },
113
+ "&:hover fieldset": {
114
+ borderColor: "primary.main",
115
+ },
116
+ },
117
+ }}
118
+ onKeyPress={(e) => {
119
+ if (e.key === "Enter") {
120
+ handleLogin();
121
+ }
122
+ }}
123
+ />
124
+ </Stack>
125
+
126
+ <Button
127
+ variant="contained"
128
+ onClick={handleLogin}
129
+ size="large"
130
+ fullWidth
131
+ sx={{
132
+ mt: 1,
133
+ py: 1.5,
134
+ fontSize: "1rem",
135
+ fontWeight: 600,
136
+ textTransform: "none",
137
+ borderRadius: 1.5,
138
+ boxShadow: (theme) =>
139
+ `0 8px 16px ${alpha(theme.palette.primary.main, 0.24)}`,
140
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
141
+ "&:hover": {
142
+ transform: "translateY(-2px)",
143
+ boxShadow: (theme) =>
144
+ `0 12px 24px ${alpha(theme.palette.primary.main, 0.32)}`,
145
+ },
146
+ "&:active": {
147
+ transform: "translateY(0px)",
148
+ },
149
+ }}
150
+ >
151
+ Sign in
152
+ </Button>
153
+
154
+ <Box
155
+ sx={{
156
+ mt: 1,
157
+ textAlign: "center",
158
+ p: 2,
159
+ borderRadius: 1.5,
160
+ bgcolor: (theme) => alpha(theme.palette.info.main, 0.08),
161
+ border: (theme) =>
162
+ `1px dashed ${alpha(theme.palette.info.main, 0.24)}`,
163
+ }}
164
+ >
165
+ <Typography
166
+ variant="caption"
167
+ color="text.secondary"
168
+ sx={{ fontSize: "0.8125rem" }}
169
+ >
170
+ Demo credentials:{" "}
171
+ <Box component="strong" sx={{ color: "text.primary" }}>
172
+ admin / admin
173
+ </Box>
174
+ </Typography>
175
+ </Box>
176
+ </Stack>
177
+ );
178
+ }
@@ -0,0 +1,44 @@
1
+ import config from "../../config/config";
2
+
3
+ import {
4
+ CognitoIdentityProviderClient,
5
+ InitiateAuthCommand,
6
+ } from "@aws-sdk/client-cognito-identity-provider";
7
+
8
+ let client = null;
9
+
10
+ function getClient() {
11
+ if (!client) {
12
+ const { credentials } = config();
13
+ if (!credentials) {
14
+ throw new Error("Cognito credentials not initialized yet");
15
+ }
16
+
17
+ client = new CognitoIdentityProviderClient({
18
+ region: credentials.region,
19
+ });
20
+
21
+ console.log("Initialized Cognito Client with region:", credentials.region);
22
+ }
23
+
24
+ return client;
25
+ }
26
+
27
+ export async function loginWithCognito(username, password) {
28
+ const { credentials } = config();
29
+ if (!credentials) {
30
+ throw new Error("CONFIG not initialized yet. Wait for CONFIG_INITIALIZED.");
31
+ }
32
+
33
+ const command = new InitiateAuthCommand({
34
+ AuthFlow: "USER_PASSWORD_AUTH",
35
+ ClientId: credentials.clientId,
36
+ AuthParameters: {
37
+ USERNAME: username,
38
+ PASSWORD: password,
39
+ },
40
+ });
41
+
42
+ const response = await getClient().send(command);
43
+ return response.AuthenticationResult;
44
+ }