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",
"dependencies": {
"@felte/reporter-solid": "^1.2.5",
"@felte/reporter-tippy": "^1.1.5",
"@felte/solid": "^1.2.7",
"@kobalte/core": "^0.6.1",
"@types/pouchdb": "^6.4.0",
"lodash": "^4.17.21",
"pouchdb": "^8.0.1",
"pouchdb-server": "^4.2.0",
"tippy.js": "^6.3.7",
"uuid": "^9.0.0"
},
"devDependencies": {
@ -2183,6 +2185,20 @@
"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": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@felte/solid/-/solid-1.2.7.tgz",
@ -2509,6 +2525,15 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"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": {
"version": "24.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
@ -10332,6 +10357,14 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

View File

@ -31,12 +31,14 @@
},
"dependencies": {
"@felte/reporter-solid": "^1.2.5",
"@felte/reporter-tippy": "^1.1.5",
"@felte/solid": "^1.2.7",
"@kobalte/core": "^0.6.1",
"@types/pouchdb": "^6.4.0",
"lodash": "^4.17.21",
"pouchdb": "^8.0.1",
"pouchdb-server": "^4.2.0",
"tippy.js": "^6.3.7",
"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 { toHex } from '~/lib/to-hex';
import { cloneDeep } from 'lodash';
import Password, { UsernamePassword } from '../password/Password';
interface Props {
values?: any;
@ -151,17 +150,6 @@ const User: Component<Props> = (props) => {
setOpenUserNamePasswordDialog(true);
};
const usernamePasswordHandler = async (
usernamePassword: UsernamePassword
) => {
console.log({
caller: 'User / usernamePasswordHandler',
props,
usernamePassword,
});
setOpenUserNamePasswordDialog(false);
};
console.log({
caller: 'User ',
props,
@ -269,12 +257,6 @@ const User: Component<Props> = (props) => {
</div>
</Dialog.Portal>
</Dialog.Root>
<Password
open={openUsernamePasswordDialog}
setOpen={setOpenUserNamePasswordDialog}
url={data('database')}
usernamePassword={usernamePasswordHandler}
/>
</>
);
};

View File

@ -19,7 +19,6 @@ form {
text-align: left;
}
/* Dialog */
.dialog__trigger {
@ -99,6 +98,14 @@ form {
font-size: 16px;
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 {
from {
opacity: 0;

View File

@ -1,6 +1,6 @@
// @refresh reload
import { Routes } from "@solidjs/router";
import { Suspense } from "solid-js";
import { Routes } from '@solidjs/router';
import { createSignal, Show, Suspense } from 'solid-js';
import {
Body,
FileRoutes,
@ -9,16 +9,16 @@ import {
Meta,
Scripts,
Title,
} from "solid-start";
import { ErrorBoundary } from "solid-start/error-boundary";
} from 'solid-start';
import { ErrorBoundary } from 'solid-start/error-boundary';
export default function Root() {
return (
<Html lang="en">
<Html lang='en'>
<Head>
<Title>SolidStart - With Vitest</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
<Title>Dyomedea users</Title>
<Meta charset='utf-8' />
<Meta name='viewport' content='width=device-width, initial-scale=1' />
</Head>
<Body>
<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 () => {
return (
<main>
<h1>Users</h1>
<Show when={!!adminCredentials()} fallback={<Credentials />}>
<Users />
</Show>
</main>
);
};