Content-Length: 613044 | pFad | https://github.com/sebadob/rauthy/commit/62d41bc2c1eb0e9b6b85f1c4528b333f8d6fb97e

51 Merge pull request #384 from sebadob/user-devices-ui · sebadob/rauthy@62d41bc · GitHub
Skip to content

Commit

Permalink
Merge pull request #384 from sebadob/user-devices-ui
Browse files Browse the repository at this point in the history
User devices UI
  • Loading branch information
sebadob authored Apr 30, 2024
2 parents 914c0a2 + ca0d33f commit 62d41bc
Show file tree
Hide file tree
Showing 18 changed files with 546 additions and 62 deletions.
47 changes: 6 additions & 41 deletions dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,13 @@

## CURRENT WORK

https://datatracker.ietf.org/doc/html/rfc8628
## TODO before v0.23.0

oauth2 device auth flow for IoT and embedded devices:

- [ ] API endpoint for initiating a device flow
- POST with `application/x-www-form-urlencoded` including:
- `client_id` long, secure, non-guessable
- `scope` optional
- response with `application/json`
- `device_code`
- `user_code`
- `verification_uri` should be short and easy to remember
- `verification_uri_complete` optional same as above with included `user_code`
- `expires_in` lifetime in seconds
- `interval` optional min client polling interval
- define error response as described in https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
- [ ] accept `urn:ietf:params:oauth:grant-type:device_code` as grant on `/token`
- [ ] API endpoints for fetching all information about an existing one
- [ ] UI to show to the user for investigation + accept
- [ ] update the .well-known with `urn:ietf:params:oauth:grant-type:device_code`
- [ ] implement the logic either into `rauthy-client` or maybe an independent new crate, if one does not
yet exist for the rust ecosystem
- [ ] create a fully working example with `rauthy` + `rauthy-client` on how to use it with a CLI tool

save devices in a new table:

- device_id generated -> added to access + refresh token
- client_id opt fk
- user_id opt fk
- created
- access_exp
- refresh_exp -> can be auto-deleted by scheduler if refresh has expired -> needs new login anyway?
- peer_ip
- authorized_by -> fk to users -> what about user deletion in the future? set null or revoke access?

new refresh tokens table for devices only:

- id
- device_id fk to devices
- nbf
- exp
- scope
- accept an optional `name` param during initial `device_code` request to have better readable first
name for new devices
- cleanup scheduler that looks for expired devices like every 24h
- UI in the user account view to see and revoke device access
- have a counterpart in the Admin UI as well? -> provide a user_id and see all devices?

## Stage 1 - essentials

Expand Down
25 changes: 25 additions & 0 deletions frontend/src/components/account/AccDevices.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script>
import Devices from "../common/Devices.svelte";
export let t;
export let sessionInfo;
</script>

<div class="container">
<!--
This is component is only a wrapper because the same Devices
is reused in the admin ui
-->
<Devices bind:t bind:userId={sessionInfo.user_id}/>
</div>

<style>
.container {
padding-left: 10px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
</style>
5 changes: 5 additions & 0 deletions frontend/src/components/account/AccMain.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import AccPassword from "./AccPassword.svelte";
import AccWebId from "./AccWebId.svelte";
import {onMount} from "svelte";
import AccDevices from "./AccDevices.svelte";
export let t;
Expand Down Expand Up @@ -80,6 +81,8 @@
<AccMFA bind:t bind:sessionInfo bind:user/>
{:else if content === 'WebID'}
<AccWebId bind:t bind:webIdData viewModePhone />
{:else if content === t.devices}
<AccDevices bind:t bind:sessionInfo />
{/if}
</div>
</div>
Expand Down Expand Up @@ -108,6 +111,8 @@
<AccMFA bind:t bind:sessionInfo bind:user />
{:else if content === 'WebID'}
<AccWebId bind:t bind:webIdData />
{:else if content === t.devices}
<AccDevices bind:t bind:sessionInfo />
{/if}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/account/AccNav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
export let showWide = false;
let labels = showWebId ?
[t.navInfo, t.navEdit, t.navPassword, t.navMfa, 'WebID', t.navLogout]
: [t.navInfo, t.navEdit, t.navPassword, t.navMfa, t.navLogout];
[t.navInfo, t.navEdit, t.navPassword, t.navMfa, 'WebID', t.devices, t.navLogout]
: [t.navInfo, t.navEdit, t.navPassword, t.navMfa, t.devices, t.navLogout];
let toggle = [];
$: if (selected) {
Expand Down
250 changes: 250 additions & 0 deletions frontend/src/components/common/Devices.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<script>
import {getUserDevices, putUserDeviceName, deleteUserDeviceRefresh} from "../../utils/dataFetching.js";
import {onMount} from "svelte";
import ExpandContainer from "$lib/ExpandContainer.svelte";
import Input from "$lib/inputs/Input.svelte";
import IconCheck from "$lib/icons/IconCheck.svelte";
import {formatDateFromTs} from "../../utils/helpers.js";
import Tooltip from "$lib/Tooltip.svelte";
import IconStop from "$lib/icons/IconStop.svelte";
import {REGEX_CLIENT_NAME} from "../../utils/constants.js";
export let t;
export let userId = '';
let devices = [];
let formErrors = {};
let formValues = {};
onMount(() => {
fetchDevices();
})
async function fetchDevices() {
let res = await getUserDevices(userId);
let body = await res.json();
if (res.ok) {
for (let device of body) {
formValues[device.id] = device.name;
}
devices = body;
} else {
console.error('error fetching devices: ' + body.message);
}
}
async function onSaveName(id) {
let newName = formValues[id];
let isValid = REGEX_CLIENT_NAME.test(newName);
if (isValid) {
formErrors[id] = '';
} else {
formErrors[id] = t?.invalidInput || 'Invalid Input';
return;
}
let data = {
device_id: id,
name: newName,
};
let res = await putUserDeviceName(userId, data);
if (res.ok) {
// update the name in memory to blend out the save button
formErrors[id] = '';
for (let device of devices) {
if (device.id === id) {
device.name = newName;
break;
}
}
devices = [...devices];
formValues[id] = newName;
} else {
let body = await res.json();
console.error(body);
}
}
async function onRevokeRefresh(id) {
let data = {
device_id: id,
};
let res = await deleteUserDeviceRefresh(userId, data);
if (res.ok) {
for (let device of devices) {
if (device.id === id) {
device.refresh_exp = undefined;
break;
}
}
devices = [...devices];
} else {
let body = await res.json();
console.error(body);
}
}
</script>
<div class="head">
{t?.devicesDesc || 'Devices linked to this account'}
</div>
<div class="devices">
{#each devices as device (device.id)}
<ExpandContainer>
<div class="device-header" slot="header">
<div class="device-head font-mono">
{device.name}
</div>
</div>
<div class="device" slot="body">
<div class="unit">
<div class="label font-label">
{t?.deviceId.toUpperCase() || 'ID'}
</div>
<div class="value font-mono">
{device.id}
</div>
</div>
<div class="row">
<Input
bind:value={formValues[device.id]}
bind:error={formErrors[device.id]}
autocomplete="off"
placeholder={t?.deviceName.toUpperCase() || 'Name'}
on:enter={() => onSaveName(device.id)}
>
{t?.deviceName.toUpperCase() || 'NAME'}
</Input>
{#if formValues[device.id] != device.name}
<Tooltip text={t?.save || 'Save'}>
<div
role="button"
tabindex="0"
class="icon-btn-input"
on:click={() => onSaveName(device.id)}
on:keypress={() => onSaveName(device.id)}
>
<IconCheck color="var(--col-ok)" width={24}/>
</div>
</Tooltip>
{/if}
</div>
<div class="unit">
<div class="label font-label">
{t?.regDate.toUpperCase() || 'REGISTRATION DATE'}
</div>
<div class="value">
{device.created}
</div>
</div>
<div class="unit">
<div class="label font-label">
{t?.accessExp.toUpperCase() || 'ACCESS EXPIRES'}
</div>
<div class="value">
{formatDateFromTs(device.access_exp)}
</div>
</div>
{#if device.refresh_exp}
<div class="unit">
<div class="label font-label">
{t?.accessRenew.toUpperCase() || 'ACCESS RENEW UNTIL'}
</div>
<div class="row">
<div class="value">
{formatDateFromTs(device.refresh_exp)}
</div>
<Tooltip text={t?.accessRenewDelete || 'Delete the possibility to renew'}>
<div
role="button"
tabindex="0"
class="icon-btn-value"
on:click={() => onRevokeRefresh(device.id)}
on:keypress={() => onRevokeRefresh(device.id)}
>
<IconStop color='var(--col-err)' width={24}/>
</div>
</Tooltip>
</div>
</div>
{/if}
<div class="unit">
<div class="label font-label">
{t?.regIp.toUpperCase() || 'REGISTRATION FROM IP'}
</div>
<div class="value">
{device.peer_ip}
</div>
</div>
</div>
</ExpandContainer>
{/each}
</div>
<style>
.head {
margin: .5rem 0;
}
.device {
margin: 0 .5rem;
}
.device-header {
display: flex;
align-items: center;
}
.device-head {
display: flex;
align-items: center;
margin: 3px 10px;
}
.devices {
width: 100%;
/*border: 1px solid red;*/
}
.label {
margin-top: 5px;
font-size: .9rem;
}
.icon-btn-input {
margin-top: 8px;
}
.icon-btn-value {
margin-left: 3px;
}
.icon-btn-input, .icon-btn-value {
cursor: pointer;
}
.row {
display: flex;
align-items: center;
}
.unit {
margin: 7px 5px;
}
.value {
display: flex;
align-items: center;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const REGEX_NAME = /^[\w\sÀ-ÿ\-]{0,32}$/gm;
export const REGEX_ATTR_DESC = /^[a-zA-Z0-9\-_/\s]{0,128}$/gm;
export const REGEX_ATTR_KEY = /^[a-zA-Z0-9\-_/]{2,32}$/gm;
export const REGEX_CLIENT_ID = /^[a-zA-Z0-9\-_/]{2,128}$/gm;
export const REGEX_CLIENT_NAME = /^[a-zA-Z0-9À-ÿ\-\s]{0,128}$/gm;
export const REGEX_CLIENT_NAME = /^[a-zA-Z0-9À-ÿ\-\s]{0,128}$/m;
export const REGEX_CONTACT = /^[a-zA-Z0-9+.@/:]{0,48}$/gm;
export const REGEX_LOWERCASE_SPACE = /^[a-z0-9-_\/\s]{2,128}$/gm;
export const REGEX_PROVIDER_SCOPE = /^[a-z0-9-_\/:\s]{0,128}$/gm;
Expand Down
Loading

0 comments on commit 62d41bc

Please sign in to comment.








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/sebadob/rauthy/commit/62d41bc2c1eb0e9b6b85f1c4528b333f8d6fb97e

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy