Refactoring to avoid requiring authentication
This commit is contained in:
parent
198653ce82
commit
a0bed34b43
|
@ -7,12 +7,14 @@
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@felte/reporter-solid": "^1.2.5",
|
"@felte/reporter-solid": "^1.2.5",
|
||||||
|
"@felte/reporter-tippy": "^1.1.5",
|
||||||
"@felte/solid": "^1.2.7",
|
"@felte/solid": "^1.2.7",
|
||||||
"@kobalte/core": "^0.6.1",
|
"@kobalte/core": "^0.6.1",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pouchdb": "^8.0.1",
|
"pouchdb": "^8.0.1",
|
||||||
"pouchdb-server": "^4.2.0",
|
"pouchdb-server": "^4.2.0",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -2183,6 +2185,20 @@
|
||||||
"solid-js": "^1.2.0"
|
"solid-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@felte/reporter-tippy": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@felte/reporter-tippy/-/reporter-tippy-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-boCEXGOkqTXRQa1l0t2Whjw/7wJHy16fA1Q3TNNj7XOiDAKYyzGfwbQn0kMR1jdzJTpSteB/xGb+ue1PenRDEQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@felte/common": "1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tippy.js": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@felte/solid": {
|
"node_modules/@felte/solid": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@felte/solid/-/solid-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@felte/solid/-/solid-1.2.7.tgz",
|
||||||
|
@ -2509,6 +2525,15 @@
|
||||||
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
|
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
|
||||||
|
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-commonjs": {
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
"version": "24.0.1",
|
"version": "24.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
|
||||||
|
@ -10332,6 +10357,14 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tippy.js": {
|
||||||
|
"version": "6.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
|
||||||
|
"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
|
|
|
@ -31,12 +31,14 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@felte/reporter-solid": "^1.2.5",
|
"@felte/reporter-solid": "^1.2.5",
|
||||||
|
"@felte/reporter-tippy": "^1.1.5",
|
||||||
"@felte/solid": "^1.2.7",
|
"@felte/solid": "^1.2.7",
|
||||||
"@kobalte/core": "^0.6.1",
|
"@kobalte/core": "^0.6.1",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pouchdb": "^8.0.1",
|
"pouchdb": "^8.0.1",
|
||||||
"pouchdb-server": "^4.2.0",
|
"pouchdb-server": "^4.2.0",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { createForm } from '@felte/solid';
|
||||||
|
import reporter from '@felte/reporter-tippy';
|
||||||
|
import { Button, Dialog, TextField } from '@kobalte/core';
|
||||||
|
import { Component, createSignal } from 'solid-js';
|
||||||
|
import 'tippy.js/dist/tippy.css';
|
||||||
|
|
||||||
|
interface CredentialsType {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
hostname?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [adminCredentials, setAdminCredentials] = createSignal<CredentialsType>();
|
||||||
|
|
||||||
|
export { adminCredentials };
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const Credentials: Component<Props> = (props) => {
|
||||||
|
const validationHandler = async (values: any) => {
|
||||||
|
const { database, username, password } = values;
|
||||||
|
let errors: any = {};
|
||||||
|
try {
|
||||||
|
const response = await fetch(database, { mode: 'cors' });
|
||||||
|
const dbStatus = await response.json();
|
||||||
|
if (!dbStatus.couchdb) {
|
||||||
|
errors.database = 'The URL is not a couchdb instance';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error({
|
||||||
|
// caller: 'Credentials / validationHandler / database root',
|
||||||
|
// error,
|
||||||
|
// });
|
||||||
|
errors.database = "Can't access the database";
|
||||||
|
}
|
||||||
|
if (!!errors.database) {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const authString = `${username}:${password}`;
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set('Authorization', 'Basic ' + btoa(authString));
|
||||||
|
const response = await fetch(`${database}//_all_dbs`, {
|
||||||
|
mode: 'cors',
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
const dbStatus = await response.json();
|
||||||
|
if (!!dbStatus.error) {
|
||||||
|
errors.username = dbStatus.reason || 'Wrong username or password';
|
||||||
|
errors.password = dbStatus.reason || 'Wrong username or password';
|
||||||
|
}
|
||||||
|
console.log({
|
||||||
|
caller: 'Credentials / validationHandler',
|
||||||
|
props,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
dbStatus,
|
||||||
|
});
|
||||||
|
// if (!dbStatus.couchdb) {
|
||||||
|
// errors.database = 'The URL is not a couchdb instance';
|
||||||
|
// }
|
||||||
|
} catch (error) {
|
||||||
|
console.error({
|
||||||
|
caller: 'Credentials / validationHandler / database root',
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
errors.username = 'Wrong username or password';
|
||||||
|
errors.password = 'Wrong username or password';
|
||||||
|
}
|
||||||
|
console.log({
|
||||||
|
caller: 'Credentials / validationHandler',
|
||||||
|
props,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
});
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitHandler = async (values: any, context: any) => {
|
||||||
|
console.log({
|
||||||
|
caller: 'Credentials / submitHandler',
|
||||||
|
props,
|
||||||
|
values,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
setAdminCredentials(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
let { form, data } = createForm({
|
||||||
|
onSubmit: submitHandler,
|
||||||
|
validate: validationHandler,
|
||||||
|
extend: reporter(),
|
||||||
|
// initialValues: passwords().get(url),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root isOpen={true}>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay class='dialog__overlay' />
|
||||||
|
<div class='dialog__positioner'>
|
||||||
|
<Dialog.Content class='dialog__content'>
|
||||||
|
<div class='dialog__header'>
|
||||||
|
<Dialog.Title class='dialog__title'>
|
||||||
|
Admin user name and password
|
||||||
|
</Dialog.Title>
|
||||||
|
<Dialog.CloseButton class='dialog__close-button'>
|
||||||
|
X
|
||||||
|
</Dialog.CloseButton>
|
||||||
|
</div>
|
||||||
|
<Dialog.Description class='dialog__description'>
|
||||||
|
<div>
|
||||||
|
Please enter the database URL and its admin username and
|
||||||
|
password.
|
||||||
|
</div>
|
||||||
|
<form use:form>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Label>Database URL</TextField.Label>
|
||||||
|
<TextField.Input
|
||||||
|
type='url'
|
||||||
|
name='database'
|
||||||
|
required={true}
|
||||||
|
placeholder='Database URL'
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Label>User name</TextField.Label>
|
||||||
|
<TextField.Input
|
||||||
|
type='text'
|
||||||
|
name='username'
|
||||||
|
required={true}
|
||||||
|
placeholder='user name'
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Label>Password</TextField.Label>
|
||||||
|
<TextField.Input
|
||||||
|
type='text'
|
||||||
|
name='password'
|
||||||
|
required={true}
|
||||||
|
placeholder='Password'
|
||||||
|
autocomplete='off'
|
||||||
|
/>
|
||||||
|
<TextField.ErrorMessage>
|
||||||
|
Please provide a valid password
|
||||||
|
</TextField.ErrorMessage>
|
||||||
|
</TextField.Root>
|
||||||
|
<Button.Root type='submit'>Save</Button.Root>
|
||||||
|
</form>
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Content>
|
||||||
|
</div>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Credentials;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default, adminCredentials } from './Credentials';
|
|
@ -1,110 +0,0 @@
|
||||||
import { createForm } from '@felte/solid';
|
|
||||||
import { Button, Dialog, TextField } from '@kobalte/core';
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import { Component, createEffect, createSignal } from 'solid-js';
|
|
||||||
|
|
||||||
export interface UsernamePassword {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
url: string;
|
|
||||||
usernamePassword: (params: UsernamePassword) => void;
|
|
||||||
open: () => boolean;
|
|
||||||
setOpen: (open: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [passwords, setPasswords] = createSignal(
|
|
||||||
new Map<string, UsernamePassword>()
|
|
||||||
);
|
|
||||||
|
|
||||||
const Password: Component<Props> = (props) => {
|
|
||||||
const { url, usernamePassword, open, setOpen } = props;
|
|
||||||
|
|
||||||
const submitHandler = async (values: any, context: any) => {
|
|
||||||
console.log({
|
|
||||||
caller: 'Password / submitHandler',
|
|
||||||
props,
|
|
||||||
values,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
const unPw = { username: values.username, password: values.password };
|
|
||||||
const newPasswords = cloneDeep(passwords());
|
|
||||||
newPasswords.set(url, unPw);
|
|
||||||
setPasswords(newPasswords);
|
|
||||||
|
|
||||||
usernamePassword(unPw);
|
|
||||||
};
|
|
||||||
|
|
||||||
let { form, data } = createForm({
|
|
||||||
onSubmit: submitHandler,
|
|
||||||
initialValues: passwords().get(url),
|
|
||||||
});
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const unPw = passwords().get(url);
|
|
||||||
console.log({
|
|
||||||
caller: 'Password / createEffect',
|
|
||||||
props,
|
|
||||||
passwords: passwords(),
|
|
||||||
unPw,
|
|
||||||
});
|
|
||||||
const newForm = createForm({
|
|
||||||
onSubmit: submitHandler,
|
|
||||||
initialValues: passwords().get(url),
|
|
||||||
});
|
|
||||||
form = newForm.form;
|
|
||||||
data = newForm.data;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog.Root isOpen={open()} onOpenChange={setOpen}>
|
|
||||||
<Dialog.Portal>
|
|
||||||
<Dialog.Overlay class='dialog__overlay' />
|
|
||||||
<div class='dialog__positioner'>
|
|
||||||
<Dialog.Content class='dialog__content'>
|
|
||||||
<div class='dialog__header'>
|
|
||||||
<Dialog.Title class='dialog__title'>
|
|
||||||
Admin user name and password
|
|
||||||
</Dialog.Title>
|
|
||||||
<Dialog.CloseButton class='dialog__close-button'>
|
|
||||||
X
|
|
||||||
</Dialog.CloseButton>
|
|
||||||
</div>
|
|
||||||
<Dialog.Description class='dialog__description'>
|
|
||||||
<div>Please enter the admin and password for {url}</div>
|
|
||||||
<form use:form>
|
|
||||||
<TextField.Root>
|
|
||||||
<TextField.Label>User name</TextField.Label>
|
|
||||||
<TextField.Input
|
|
||||||
type='text'
|
|
||||||
name='username'
|
|
||||||
required={true}
|
|
||||||
placeholder='user name'
|
|
||||||
/>
|
|
||||||
</TextField.Root>
|
|
||||||
<TextField.Root>
|
|
||||||
<TextField.Label>Password</TextField.Label>
|
|
||||||
<TextField.Input
|
|
||||||
type='text'
|
|
||||||
name='password'
|
|
||||||
required={true}
|
|
||||||
placeholder='Password'
|
|
||||||
autocomplete='off'
|
|
||||||
/>
|
|
||||||
<TextField.ErrorMessage>
|
|
||||||
Please provide a valid password
|
|
||||||
</TextField.ErrorMessage>
|
|
||||||
</TextField.Root>
|
|
||||||
<Button.Root type='submit'>Save and create</Button.Root>
|
|
||||||
</form>
|
|
||||||
</Dialog.Description>
|
|
||||||
</Dialog.Content>
|
|
||||||
</div>
|
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Password;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default, UsernamePassword } from './Password';
|
|
|
@ -10,7 +10,6 @@ import { put } from '~/lib/db';
|
||||||
import { useNavigate } from 'solid-start';
|
import { useNavigate } from 'solid-start';
|
||||||
import { toHex } from '~/lib/to-hex';
|
import { toHex } from '~/lib/to-hex';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import Password, { UsernamePassword } from '../password/Password';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
values?: any;
|
values?: any;
|
||||||
|
@ -151,17 +150,6 @@ const User: Component<Props> = (props) => {
|
||||||
setOpenUserNamePasswordDialog(true);
|
setOpenUserNamePasswordDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const usernamePasswordHandler = async (
|
|
||||||
usernamePassword: UsernamePassword
|
|
||||||
) => {
|
|
||||||
console.log({
|
|
||||||
caller: 'User / usernamePasswordHandler',
|
|
||||||
props,
|
|
||||||
usernamePassword,
|
|
||||||
});
|
|
||||||
setOpenUserNamePasswordDialog(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log({
|
console.log({
|
||||||
caller: 'User ',
|
caller: 'User ',
|
||||||
props,
|
props,
|
||||||
|
@ -269,12 +257,6 @@ const User: Component<Props> = (props) => {
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
<Password
|
|
||||||
open={openUsernamePasswordDialog}
|
|
||||||
setOpen={setOpenUserNamePasswordDialog}
|
|
||||||
url={data('database')}
|
|
||||||
usernamePassword={usernamePasswordHandler}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,6 @@ form {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Dialog */
|
/* Dialog */
|
||||||
|
|
||||||
.dialog__trigger {
|
.dialog__trigger {
|
||||||
|
@ -99,6 +98,14 @@ form {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: hsl(240 5% 26%);
|
color: hsl(240 5% 26%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog__description > div {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: hsl(240 6% 10%);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes overlayShow {
|
@keyframes overlayShow {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
16
src/root.tsx
16
src/root.tsx
|
@ -1,6 +1,6 @@
|
||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { Routes } from "@solidjs/router";
|
import { Routes } from '@solidjs/router';
|
||||||
import { Suspense } from "solid-js";
|
import { createSignal, Show, Suspense } from 'solid-js';
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
FileRoutes,
|
FileRoutes,
|
||||||
|
@ -9,16 +9,16 @@ import {
|
||||||
Meta,
|
Meta,
|
||||||
Scripts,
|
Scripts,
|
||||||
Title,
|
Title,
|
||||||
} from "solid-start";
|
} from 'solid-start';
|
||||||
import { ErrorBoundary } from "solid-start/error-boundary";
|
import { ErrorBoundary } from 'solid-start/error-boundary';
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
<Html lang="en">
|
<Html lang='en'>
|
||||||
<Head>
|
<Head>
|
||||||
<Title>SolidStart - With Vitest</Title>
|
<Title>Dyomedea users</Title>
|
||||||
<Meta charset="utf-8" />
|
<Meta charset='utf-8' />
|
||||||
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
<Meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||||
</Head>
|
</Head>
|
||||||
<Body>
|
<Body>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
import Users from "~/components/users";
|
import { createEffect, Show } from 'solid-js';
|
||||||
|
import Credentials, { adminCredentials } from '~/components/credentials';
|
||||||
|
import Users from '~/components/users';
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log({
|
||||||
|
caller: 'user/index / createEffect',
|
||||||
|
adminCredentials: adminCredentials(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1>Users</h1>
|
<h1>Users</h1>
|
||||||
|
<Show when={!!adminCredentials()} fallback={<Credentials />}>
|
||||||
<Users />
|
<Users />
|
||||||
|
</Show>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue