add ability of to create custom token closes #82
This commit is contained in:
parent
095a37189d
commit
00e862e016
29
users.go
29
users.go
@ -341,10 +341,37 @@ func usersEndpints(db *sql.DB, handle *Handle) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type NewToken struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
ExpiryTime int `json:"expiry"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
}
|
||||||
|
PostAuthJson(handle, "/user/token/add", User_Normal, func(c *Context, obj *NewToken) *Error {
|
||||||
|
// TODO handle this for admin
|
||||||
|
|
||||||
|
token, generated := generateToken(c.Db, c.User.Email, obj.Password, obj.Name)
|
||||||
|
if !generated {
|
||||||
|
return c.JsonBadRequest("Password provided is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.Db.Exec("update tokens set time_to_live=$1 where token=$2 and user_id=$3", obj.ExpiryTime, token, c.User.Id)
|
||||||
|
if err != nil {
|
||||||
|
return c.E500M("Failed to update token info", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendJSON(struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ExpiryTime int `json:"expiry"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}{
|
||||||
|
obj.Name, obj.ExpiryTime, token,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
type TokenDelete struct {
|
type TokenDelete struct {
|
||||||
Time time.Time `json:"time" validate:"required"`
|
Time time.Time `json:"time" validate:"required"`
|
||||||
}
|
}
|
||||||
DeleteAuthJson(handle, "/user/token", 1, func(c *Context, obj *TokenDelete) *Error {
|
DeleteAuthJson(handle, "/user/token", User_Normal, func(c *Context, obj *TokenDelete) *Error {
|
||||||
// TODO allow admin user to delete to other persons token
|
// TODO allow admin user to delete to other persons token
|
||||||
|
|
||||||
err := deleteToken(c.Db, c.User.Id, obj.Time)
|
err := deleteToken(c.Db, c.User.Id, obj.Time)
|
||||||
|
Binary file not shown.
@ -14,8 +14,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.5.6",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "3.0.0",
|
||||||
"@types/eslint": "^8.56.0",
|
"@types/eslint": "^8.56.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"prettier-plugin-svelte": "^3.1.2",
|
"prettier-plugin-svelte": "^3.1.2",
|
||||||
"sass": "^1.71.1",
|
"sass": "^1.71.1",
|
||||||
"svelte": "^5.0.0-next.1",
|
"svelte": "^5.0.0-next.102",
|
||||||
"svelte-check": "^3.6.0",
|
"svelte-check": "^3.6.0",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.4.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
@ -33,7 +33,6 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2"
|
||||||
"svelte-chartjs": "^3.1.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2115
webpage/pnpm-lock.yaml
Normal file
2115
webpage/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
48
webpage/src/lib/Tooltip.svelte
Normal file
48
webpage/src/lib/Tooltip.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { title } = $props<{ title: string }>();
|
||||||
|
|
||||||
|
let isHovered = $state(false);
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
|
||||||
|
let div: HTMLDivElement;
|
||||||
|
|
||||||
|
function mouseOver(event: MouseEvent) {
|
||||||
|
isHovered = true;
|
||||||
|
x = event.pageX + 5;
|
||||||
|
y = event.pageY + 5;
|
||||||
|
}
|
||||||
|
function focus() {
|
||||||
|
isHovered = true;
|
||||||
|
|
||||||
|
const box = div.getBoundingClientRect();
|
||||||
|
x = box.x + 5;
|
||||||
|
y = box.y;
|
||||||
|
}
|
||||||
|
function mouseMove(event: MouseEvent) {
|
||||||
|
x = event.pageX + 5;
|
||||||
|
y = event.pageY + 5;
|
||||||
|
}
|
||||||
|
function mouseLeave() {
|
||||||
|
isHovered = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={div} on:mouseover={mouseOver} on:mouseleave={mouseLeave} on:mousemove={mouseMove} on:focus={focus} role="tooltip">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isHovered}
|
||||||
|
<div style="top: {y}px; left: {x}px;" class="tooltip">{title}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tooltip {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
box-shadow: 1px 1px 1px #ddd;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
132
webpage/src/routes/user/info/AddToken.svelte
Normal file
132
webpage/src/routes/user/info/AddToken.svelte
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {createEventDispatcher} from 'svelte';
|
||||||
|
import MessageSimple from 'src/lib/MessageSimple.svelte';
|
||||||
|
import Tooltip from 'src/lib/Tooltip.svelte';
|
||||||
|
import 'src/styles/forms.css';
|
||||||
|
import {post} from 'src/lib/requests.svelte';
|
||||||
|
import Spinner from 'src/lib/Spinner.svelte';
|
||||||
|
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{reload: void}>();
|
||||||
|
|
||||||
|
let addNewToken = $state(false);
|
||||||
|
|
||||||
|
let messages: MessageSimple;
|
||||||
|
|
||||||
|
let expiry_date: HTMLInputElement = $state(undefined as any);
|
||||||
|
|
||||||
|
type NewToken = {
|
||||||
|
name: string,
|
||||||
|
expiry: number,
|
||||||
|
token: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
let token: Promise<NewToken> | undefined = $state();
|
||||||
|
|
||||||
|
let newToken: {
|
||||||
|
name: string;
|
||||||
|
expiry: string;
|
||||||
|
password: string;
|
||||||
|
} = $state({
|
||||||
|
name: '',
|
||||||
|
expiry: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createToken(e: SubmitEvent & { currentTarget: HTMLFormElement }) {
|
||||||
|
let expiry: number = 0;
|
||||||
|
expiry_date.setCustomValidity('');
|
||||||
|
if (newToken.expiry == '') {
|
||||||
|
expiry = -1;
|
||||||
|
} else {
|
||||||
|
expiry = Number(newToken.expiry);
|
||||||
|
if (isNaN(expiry)) {
|
||||||
|
expiry_date.setCustomValidity('Invalid Number');
|
||||||
|
e.currentTarget.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await post("user/token/add", {
|
||||||
|
name: newToken.name,
|
||||||
|
expiry: expiry,
|
||||||
|
password: newToken.password,
|
||||||
|
});
|
||||||
|
token = Promise.resolve(r)
|
||||||
|
setTimeout(() => dispatch('reload'), 500)
|
||||||
|
} catch (e) {
|
||||||
|
token = undefined;
|
||||||
|
console.error("Notify user", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (expiry_date) {
|
||||||
|
if (isNaN(Number(newToken.expiry))) {
|
||||||
|
expiry_date.setCustomValidity('Invalid Number');
|
||||||
|
} else {
|
||||||
|
expiry_date.setCustomValidity('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if addNewToken}
|
||||||
|
<div>
|
||||||
|
<h2>Add New Token</h2>
|
||||||
|
{#if !token}
|
||||||
|
<form on:submit|preventDefault={createToken}>
|
||||||
|
<fieldset>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input required bind:value={newToken.name} name="name" />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="expiry_date">Expiry Date</label>
|
||||||
|
<div class="flex">
|
||||||
|
<input bind:this={expiry_date} bind:value={newToken.expiry} name="expiry_date" />
|
||||||
|
<Tooltip title="Time in seconds. Leave empty to last forever">
|
||||||
|
<span class="center-question bi bi-question-circle-fill" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input required bind:value={newToken.password} name="password" />
|
||||||
|
</fieldset>
|
||||||
|
<MessageSimple bind:this={messages} />
|
||||||
|
<div>
|
||||||
|
<button> Update </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{:else}
|
||||||
|
{#await token}
|
||||||
|
<Spinner /> Generating
|
||||||
|
{:then t}
|
||||||
|
<h3> Token generated </h3>
|
||||||
|
<form on:submit|preventDefault={() => {}}>
|
||||||
|
<fieldset>
|
||||||
|
<label for="token">Token</label>
|
||||||
|
<div class="flex">
|
||||||
|
<input value={t.token} on:input={(e) => e.preventDefault() } name="token" />
|
||||||
|
<div style="width: 5em;">
|
||||||
|
<button on:click={() => navigator.clipboard.writeText(t.token)} >
|
||||||
|
<span class="bi bi-clipboard" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<div>
|
||||||
|
<button on:click={() => token = undefined}> Generate new token </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{:catch e}
|
||||||
|
{e}
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
<button class="expander" on:click={() => (addNewToken = true)}> Add New Token </button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -10,6 +10,8 @@
|
|||||||
import { post, rdelete } from 'src/lib/requests.svelte';
|
import { post, rdelete } from 'src/lib/requests.svelte';
|
||||||
import { userStore } from 'src/routes/UserStore.svelte';
|
import { userStore } from 'src/routes/UserStore.svelte';
|
||||||
|
|
||||||
|
import AddToken from './AddToken.svelte';
|
||||||
|
|
||||||
let page = $state(0);
|
let page = $state(0);
|
||||||
let showNext = $state(false);
|
let showNext = $state(false);
|
||||||
let token_list: Token[] = $state([]);
|
let token_list: Token[] = $state([]);
|
||||||
@ -95,6 +97,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AddToken on:reload={getList} />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.buttons {
|
.buttons {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -37,6 +37,21 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.expander {
|
||||||
|
background: none;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 1px #ccc;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.expander::after {
|
||||||
|
content: '▼'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
a.button {
|
a.button {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -144,4 +144,10 @@ form {
|
|||||||
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
|
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-question {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 5px;
|
||||||
|
margin-top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user