Refactoring to avoid requiring authentication

This commit is contained in:
Eric van der Vlist 2023-02-23 22:25:31 +01:00
parent 198653ce82
commit a0bed34b43
10 changed files with 222 additions and 140 deletions

33
package-lock.json generated
View File

@ -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",

View File

@ -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"
} }
} }

View File

@ -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;

View File

@ -0,0 +1 @@
export { default, adminCredentials } from './Credentials';

View File

@ -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;

View File

@ -1 +0,0 @@
export { default, UsernamePassword } from './Password';

View File

@ -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}
/>
</> </>
); );
}; };

View File

@ -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;

View File

@ -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>

View File

@ -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>
<Users /> <Show when={!!adminCredentials()} fallback={<Credentials />}>
<Users />
</Show>
</main> </main>
); );
}; };