summaryrefslogtreecommitdiffstats
path: root/src/webapp
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-01-23 11:41:42 +0100
committerivarlovlie <git@ivarlovlie.no>2022-01-23 14:33:05 +0100
commitce86d103039b22695b04714ee85e9ef3e1e032b5 (patch)
tree557455780de06ceb95dd556ca5ffca0208a1f8ba /src/webapp
parent89816382424e59ad953b433fbf82c925741b3136 (diff)
downloadbookmark-thing-ce86d103039b22695b04714ee85e9ef3e1e032b5.tar.xz
bookmark-thing-ce86d103039b22695b04714ee85e9ef3e1e032b5.zip
feat(auth): Implements first draft of basic auth gen/validation
Diffstat (limited to 'src/webapp')
-rw-r--r--src/webapp/src/components/carbon-extras/NativeDateInput.svelte21
-rw-r--r--src/webapp/src/lib/api/account.ts26
-rw-r--r--src/webapp/src/lib/configuration.ts6
-rw-r--r--src/webapp/src/lib/models/IAccessToken.ts9
-rw-r--r--src/webapp/src/lib/models/ICreateTokenRequest.ts7
-rw-r--r--src/webapp/src/routes/app/_header.svelte11
-rw-r--r--src/webapp/src/routes/app/modals/access-tokens-modal.svelte148
-rw-r--r--src/webapp/src/routes/app/modals/new-access-token-modal.svelte131
8 files changed, 356 insertions, 3 deletions
diff --git a/src/webapp/src/components/carbon-extras/NativeDateInput.svelte b/src/webapp/src/components/carbon-extras/NativeDateInput.svelte
new file mode 100644
index 0000000..ccdec53
--- /dev/null
+++ b/src/webapp/src/components/carbon-extras/NativeDateInput.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+ import {generate_unsafe_id} from "@/lib/helpers";
+
+ export let value;
+ export let minDate;
+ export let label;
+ export let id = generate_unsafe_id(9);
+</script>
+
+<div class="bx--date-picker-container">
+ <label for="{id}"
+ class="bx--label">{label}</label>
+ <div class="bx--date-picker-input__wrapper">
+ <input id="{id}"
+ type="date"
+ class="bx--date-picker__input"
+ placeholder="mm/dd/yyyy"
+ min={minDate}
+ bind:value>
+ </div>
+</div>
diff --git a/src/webapp/src/lib/api/account.ts b/src/webapp/src/lib/api/account.ts
index f1c1708..eaf7592 100644
--- a/src/webapp/src/lib/api/account.ts
+++ b/src/webapp/src/lib/api/account.ts
@@ -1,5 +1,6 @@
import {api_base} from "@/lib/configuration";
import type {ICreateSessionRequest} from "@/lib/models/ICreateSessionRequest";
+import type {ICreateTokenRequest} from "@/lib/models/ICreateTokenRequest";
export async function create_session_async(request: ICreateSessionRequest): Promise<Response> {
return fetch(api_base("account/create-session"), {
@@ -25,3 +26,28 @@ export async function end_session_async(): Promise<Response> {
credentials: "include"
});
}
+
+export async function get_tokens_async(): Promise<Response> {
+ return fetch(api_base("account/tokens"), {
+ method: "get",
+ credentials: "include"
+ });
+}
+
+export async function create_token_async(request: ICreateTokenRequest): Promise<Response> {
+ return fetch(api_base("account/create-token"), {
+ method: "post",
+ credentials: "include",
+ body: JSON.stringify(request),
+ headers: {
+ "Content-Type": "application/json;charset=UTF-8"
+ }
+ });
+}
+
+export async function delete_token_async(token_id: string): Promise<Response> {
+ return fetch(api_base("account/delete-token?id=" + token_id), {
+ method: "delete",
+ credentials: "include"
+ });
+}
diff --git a/src/webapp/src/lib/configuration.ts b/src/webapp/src/lib/configuration.ts
index e7dbcfe..2b9c2af 100644
--- a/src/webapp/src/lib/configuration.ts
+++ b/src/webapp/src/lib/configuration.ts
@@ -1,7 +1,9 @@
-const api_version = "v1/";
+export const api_version = "v1";
+export const api_docs = "http://localhost:5003/swagger/index.html";
+export const api_url = "http://localhost:5003";
export function api_base(path) {
- return is_development() ? "http://localhost:5003/" + api_version + path : "/" + api_version + path;
+ return `${api_url}/${api_version}/${path}`;
}
export function is_development() {
diff --git a/src/webapp/src/lib/models/IAccessToken.ts b/src/webapp/src/lib/models/IAccessToken.ts
new file mode 100644
index 0000000..218f888
--- /dev/null
+++ b/src/webapp/src/lib/models/IAccessToken.ts
@@ -0,0 +1,9 @@
+export interface IAccessToken {
+ id: string,
+ created: Date,
+ expiry_date: Date,
+ allow_read: boolean;
+ allow_create: boolean;
+ allow_update: boolean;
+ allow_delete: boolean;
+}
diff --git a/src/webapp/src/lib/models/ICreateTokenRequest.ts b/src/webapp/src/lib/models/ICreateTokenRequest.ts
new file mode 100644
index 0000000..6e3c7a5
--- /dev/null
+++ b/src/webapp/src/lib/models/ICreateTokenRequest.ts
@@ -0,0 +1,7 @@
+export interface ICreateTokenRequest {
+ allow_read: boolean;
+ allow_create: boolean;
+ allow_update: boolean;
+ allow_delete: boolean;
+ expiry_date: Date;
+}
diff --git a/src/webapp/src/routes/app/_header.svelte b/src/webapp/src/routes/app/_header.svelte
index 4b2ddb7..04eb405 100644
--- a/src/webapp/src/routes/app/_header.svelte
+++ b/src/webapp/src/routes/app/_header.svelte
@@ -12,11 +12,13 @@
import Help16 from "carbon-icons-svelte/lib/Help16";
import {end_session_async} from "@/lib/api/account";
import {clear_entries} from "@/lib/stores/entries";
+ import AccessTokensModal from "./modals/access-tokens-modal.svelte";
let profile_dropdown_is_open = false;
let options_dropdown_is_open = false;
let enable_dark_theme = get(preferences).theme === ApplicationTheme.DARK;
let enable_site_report = get(preferences).enable_site_report;
+ let access_token_modal_is_open = false;
preferences.subscribe(e => {
enable_site_report = e.enable_site_report;
@@ -43,7 +45,11 @@
}
function manage_access_tokens() {
- alert("Not implemented");
+ access_token_modal_is_open = true;
+ }
+
+ function on_access_tokens_modal_close() {
+ access_token_modal_is_open = false;
}
function toggle_site_report() {
@@ -62,6 +68,9 @@
}
</script>
+<AccessTokensModal open={access_token_modal_is_open}
+ on:close={on_access_tokens_modal_close}/>
+
<Header company="IOL"
platformName="Bookmark Thing">
<HeaderUtilities>
diff --git a/src/webapp/src/routes/app/modals/access-tokens-modal.svelte b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte
index e69de29..af4b524 100644
--- a/src/webapp/src/routes/app/modals/access-tokens-modal.svelte
+++ b/src/webapp/src/routes/app/modals/access-tokens-modal.svelte
@@ -0,0 +1,148 @@
+<script lang="ts">
+ import {delete_token_async, get_tokens_async} from "@/lib/api/account";
+ import {Modal, DataTable, ToolbarContent, Toolbar, ToolbarMenu, ToolbarMenuItem, Button, OverflowMenu, OverflowMenuItem, CodeSnippet, Link} from "carbon-components-svelte";
+ import {onMount} from "svelte";
+ import type {IAccessToken} from "@/lib/models/IAccessToken";
+ import {api_docs} from "@/lib/configuration";
+ import NewAccessTokenModal from "./new-access-token-modal.svelte";
+
+ export let open;
+ let loading_tokens_table;
+ let tokens = [] as Array<IAccessToken>;
+ let create_access_token_modal_is_open = false;
+ let copy_access_token_modal_is_open = false;
+ let new_access_token = "";
+ const table_headers = [
+ {key: "created", value: "Created"},
+ {key: "expiry_date", value: "Expires"},
+ {key: "allow_create", value: "Create"},
+ {key: "allow_read", value: "Read"},
+ {key: "allow_update", value: "Update"},
+ {key: "allow_delete", value: "Delete"},
+ {key: "overflow", empty: true},
+ ];
+
+ async function load_tokens() {
+ loading_tokens_table = true;
+ const get_tokens_request = await get_tokens_async();
+ loading_tokens_table = false;
+ if (get_tokens_request.ok) {
+ const response_json = await get_tokens_request.json();
+ if (response_json?.length > 0) {
+ let temparr = [];
+ for (const token of response_json as Array<IAccessToken>) {
+ temparr.push({
+ id: token.id,
+ expiry_date: new Date(token.expiry_date).toLocaleDateString(),
+ created: new Date(token.created).toLocaleDateString(),
+ allow_read: token.allow_read,
+ allow_create: token.allow_create,
+ allow_update: token.allow_update,
+ allow_delete: token.allow_delete,
+ });
+ }
+ tokens = temparr;
+ } else {
+ tokens = [];
+ }
+ } else {
+ tokens = [];
+ }
+ }
+
+ async function delete_token(event) {
+ const row = event.target.closest("tr");
+ const id = row.id.substring(4, row.id.length);
+ if (confirm("Are you sure you want to delete this token?")) {
+ const http_request = await delete_token_async(id);
+ if (http_request.ok) {
+ await load_tokens();
+ }
+ }
+ }
+
+ async function delete_all_tokens() {
+ if (confirm("Are you sure you want to delete all tokens?")) {
+ for (const token of tokens) {
+ await delete_token_async(token.id);
+ }
+ await load_tokens();
+ }
+ }
+
+ async function handle_token_created(e) {
+ const token = e.detail;
+ console.log("Created token", token);
+ create_access_token_modal_is_open = false;
+ new_access_token = token;
+ copy_access_token_modal_is_open = true;
+ await load_tokens();
+ }
+
+ function copy_access_token_modal_closed() {
+ new_access_token = "";
+ copy_access_token_modal_is_open = false;
+ }
+
+ onMount(async () => {
+ await load_tokens();
+ });
+</script>
+
+<Modal size="lg"
+ passiveModal="{true}"
+ {open}
+ preventCloseOnClickOutside
+ modalHeading="Manage access tokens"
+ on:open
+ on:close>
+ <DataTable size="compact"
+ sortable="{true}"
+ headers={table_headers}
+ style="padding-bottom: 50px"
+ rows={tokens}>
+ <Toolbar size="sm">
+ <ToolbarContent>
+ <ToolbarMenu>
+ <ToolbarMenuItem>
+ <Link href="{api_docs}"
+ target="_blank">API documentation
+ </Link>
+ </ToolbarMenuItem>
+ <ToolbarMenuItem danger
+ on:click={delete_all_tokens}>
+ Delete all tokens
+ </ToolbarMenuItem>
+ </ToolbarMenu>
+ <Button on:click={() => create_access_token_modal_is_open = true}>Create token</Button>
+ </ToolbarContent>
+ </Toolbar>
+ <svelte:fragment slot="cell"
+ let:cell>
+ {#if cell.key === "overflow"}
+ <OverflowMenu flipped
+ size="sm">
+ <OverflowMenuItem danger
+ on:click={delete_token}
+ text="Delete"/>
+ </OverflowMenu>
+ {:else}
+ {cell.value}
+ {/if}
+ </svelte:fragment>
+ </DataTable>
+</Modal>
+
+<Modal size="sm"
+ passiveModal="{true}"
+ preventCloseOnClickOutside
+ modalHeading="Your access token"
+ modalDescription="This will only be shown once"
+ on:close={copy_access_token_modal_closed}
+ open={copy_access_token_modal_is_open}>
+ <CodeSnippet code={new_access_token}/>
+ <br>
+</Modal>
+
+<NewAccessTokenModal bind:open={create_access_token_modal_is_open}
+ on:created={handle_token_created}/>
diff --git a/src/webapp/src/routes/app/modals/new-access-token-modal.svelte b/src/webapp/src/routes/app/modals/new-access-token-modal.svelte
new file mode 100644
index 0000000..8999b0b
--- /dev/null
+++ b/src/webapp/src/routes/app/modals/new-access-token-modal.svelte
@@ -0,0 +1,131 @@
+<script lang="ts">
+ import {Checkbox, Form, FormGroup, Modal} from "carbon-components-svelte";
+ import NativeDateInput from "@/components/carbon-extras/NativeDateInput.svelte";
+ import type {ICreateTokenRequest} from "@/lib/models/ICreateTokenRequest";
+ import {create_token_async} from "@/lib/api/account";
+ import {createEventDispatcher} from "svelte";
+
+ export let open = false;
+ const default_expires = (new Date().setTime(new Date().getTime() + 1));
+ const dispatch = createEventDispatcher();
+ const form = {
+ error: "",
+ loading: false,
+ expiry_date: {
+ value: default_expires,
+ error: "",
+ warning: "",
+ reset() {
+ form.expiry_date.value = default_expires;
+ },
+ change() {
+ },
+ validate() {
+ }
+ },
+ allow_create: {
+ value: false,
+ error: "",
+ warning: "",
+ reset() {
+ form.allow_create.value = false;
+ },
+ change() {
+ },
+ validate() {
+ }
+ },
+ allow_read: {
+ value: false,
+ error: "",
+ warning: "",
+ reset() {
+ form.allow_read.value = false;
+ },
+ change() {
+ },
+ validate() {
+ }
+ },
+ allow_update: {
+ value: false,
+ error: "",
+ warning: "",
+ reset() {
+ form.allow_update.value = false;
+ },
+ change() {
+ },
+ validate() {
+ }
+ },
+ allow_delete: {
+ value: false,
+ error: "",
+ warning: "",
+ reset() {
+ form.allow_delete.value = false;
+ },
+ change() {
+ },
+ validate() {
+ }
+ },
+ reset() {
+ form.allow_read.reset();
+ form.allow_delete.reset();
+ form.allow_update.reset();
+ form.allow_create.reset();
+ form.expiry_date.reset();
+ },
+ payload(): ICreateTokenRequest {
+ return {
+ allow_delete: form.allow_delete.value,
+ allow_read: form.allow_read.value,
+ allow_update: form.allow_update.value,
+ allow_create: form.allow_create.value,
+ expiry_date: form.expiry_date.value,
+ } as ICreateTokenRequest;
+ },
+ async submit() {
+ form.loading = true;
+ const http_request = await create_token_async(form.payload());
+ form.loading = false;
+ if (http_request.ok) {
+ dispatch("created", await http_request.text());
+ }
+ }
+ };
+</script>
+
+<Modal modalHeading="Create access token"
+ preventCloseOnClickOutside
+ hasForm
+ primaryButtonText="Submit"
+ secondaryButtonText="Cancel"
+ style="padding-bottom: 50px;padding-right: 30px;"
+ on:click:button--secondary={() => open = false}
+ bind:open={open}
+ on:submit={form.submit}>
+ <Form>
+ <FormGroup>
+ <NativeDateInput label="Expiry date"
+ minDate="{new Date()}"
+ bind:value={form.expiry_date.value}/>
+ </FormGroup>
+ <FormGroup legendText="Permissions">
+ <Checkbox id="permission-create"
+ labelText="Create"
+ bind:checked="{form.allow_create.value}"/>
+ <Checkbox id="permission-read"
+ labelText="Read"
+ bind:checked="{form.allow_read.value}"/>
+ <Checkbox id="permission-update"
+ labelText="Update"
+ bind:checked="{form.allow_update.value}"/>
+ <Checkbox id="permission-delete"
+ labelText="Delete"
+ bind:checked="{form.allow_delete.value}"/>
+ </FormGroup>
+ </Form>
+</Modal>