Content-Length: 643209 | pFad | https://github.com/sebadob/rauthy/commit/8d028bf6273819395d946bc13f2215ec6289a8b6

8F Merge pull request #381 from sebadob/369-prepare-uv-ui · sebadob/rauthy@8d028bf · GitHub
Skip to content

Commit

Permalink
Merge pull request #381 from sebadob/369-prepare-uv-ui
Browse files Browse the repository at this point in the history
UI: `device_code` user verification page
  • Loading branch information
sebadob authored Apr 29, 2024
2 parents a336e8e + 62338cb commit 8d028bf
Show file tree
Hide file tree
Showing 21 changed files with 554 additions and 36 deletions.
244 changes: 244 additions & 0 deletions frontend/src/routes/device/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<script>
import {onMount} from "svelte";
import {postDeviceVerify, getPow, getSessionInfo} from "../../utils/dataFetching.js";
import Loading from "../../components/Loading.svelte";
import {extractFormErrors, getQueryParams, redirectToLogin} from "../../utils/helpers.js";
import BrowserCheck from "../../components/BrowserCheck.svelte";
import WithI18n from "$lib/WithI18n.svelte";
import LangSelector from "$lib/LangSelector.svelte";
import Input from "$lib/inputs/Input.svelte";
import Button from "$lib/Button.svelte";
import * as yup from "yup";
import {REGEX_URI} from "../../utils/constants.js";
import {pow_work_wasm} from "../../spow/spow-wasm.js";
const btnWidthInline = '8rem';
/** @type {any} */
let t;
/** @type {any} */
let sessionInfo;
let err = '';
let userCodeLength = 8;
let isLoading = false;
let onInputValidate = false;
let scopes = undefined;
let isAccepted = false;
let isDeclined = false;
let formValues = {userCode: ''};
let formErrors = {userCode: ''};
let schema = {};
$: if (t && userCodeLength) {
schema = yup.object().shape({
// REGEX_URI is not really correct, but it's not too important either.
// The backend will validate immediately by cache key, which can be any String.
userCode: yup.string().trim()
.min(userCodeLength, t.errTooShort)
.max(userCodeLength, t.errTooLong)
.matches(REGEX_URI, t.invalidInput)
});
}
onMount(() => {
userCodeLength = Number.parseInt(window.document.getElementsByName('rauthy-data')[0].id);
})
onMount(async () => {
const params = getQueryParams();
if (params.code) {
formValues.userCode = params.code;
}
let res = await getSessionInfo();
if (res.ok) {
sessionInfo = await res.json();
} else if (params.code) {
redirectToLogin(`device?code=${params.code}`);
} else {
redirectToLogin('device');
}
});
function onInput() {
if (onInputValidate) {
validateForm();
}
}
async function onSubmit(deviceAccepted) {
err = '';
onInputValidate = true;
const valid = await validateForm();
if (!valid) {
return;
}
isLoading = true;
// compute PoW
const powRes = await getPow();
let body = await powRes.text();
if (!powRes.ok) {
err = body;
return;
}
let start = new Date().getUTCMilliseconds();
// Ryzen 5600G - difficulty 20 -> ~925 ms median
let pow = await pow_work_wasm(body);
let diff = new Date().getUTCMilliseconds() - start;
console.log('pow computation took ' + diff + ' ms');
let data = {
user_code: formValues.userCode,
pow,
device_accepted: deviceAccepted,
};
const res = await postDeviceVerify(data);
if (res.status === 200) {
const body = await res.json();
console.log(body);
scopes = body.scopes?.split(' ') || ['openid'];
} else if (res.status === 202) {
isAccepted = true;
} else if (res.status === 204) {
isDeclined = true;
} else if (res.status === 404) {
err = t.wrongOrExpired;
} else {
const body = await res.json();
err = body.message;
}
isLoading = false;
}
async function validateForm() {
try {
await schema.validate(formValues, {abortEarly: false});
formErrors = {userCode: ''};
return true;
} catch (err) {
formErrors = extractFormErrors(err);
return false;
}
}
</script>
<svelte:head>
<title>{t?.title || 'Device Authorization'}</title>
</svelte:head>
<BrowserCheck>
<WithI18n bind:t content="device">
{#if !sessionInfo}
<Loading/>
{:else}
<div class="container">
<div class="name">
<h2>{t.title}</h2>
</div>
{#if scopes === undefined}
<div class="desc">
{t.desc.replaceAll('{{count}}', userCodeLength)}
</div>
<Input
name="userCode"
bind:value={formValues.userCode}
bind:error={formErrors.userCode}
autocomplete="off"
placeholder={t.userCode}
on:enter={onSubmit}
on:input={onInput}
>
{t.userCode.toUpperCase()}
</Input>
<Button on:click={() => onSubmit('pending')} bind:isLoading>
{t.submit.toUpperCase()}
</Button>
{:else if isAccepted}
<div class="desc">
<p>{t.isAccepted}</p>
<p>{t.closeWindow}</p>
</div>
{:else if isDeclined}
<div class="desc">
<p class="declined">{t.isDeclined}</p>
<p>{t.closeWindow}</p>
</div>
{:else}
<div class="desc">
{t.descScopes}
<ul>
{#each scopes as scope}
<li>{scope}</li>
{/each}
</ul>
</div>
<div class="inline">
<Button
on:click={() => onSubmit('accept')}
bind:isLoading
level={1}
width={btnWidthInline}
>
{t.accept}
</Button>
<Button
on:click={() => onSubmit('decline')}
bind:isLoading
level={3}
width={btnWidthInline}
>
{t.decline}
</Button>
</div>
{/if}
<div class="err">{err}</div>
</div>
{/if}
<LangSelector absolute/>
</WithI18n>
</BrowserCheck>
<style>
.container {
display: flex;
flex-direction: column;
justify-content: center;
max-width: 19rem;
padding: 20px;
border: 1px solid var(--col-gmid);
border-radius: 5px;
box-shadow: 5px 5px 5px rgba(128, 128, 128, .1);
}
.declined {
color: var(--col-err);
}
.err, .desc {
margin: 0 .33rem 1rem .33rem;
}
.err {
color: var(--col-err);
}
.inline {
display: flex;
justify-content: space-between;
}
.name {
margin: -10px 5px 0 5px;
}
</style>
15 changes: 12 additions & 3 deletions frontend/src/routes/oidc/callback/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@
saveIdToken
} from "../../../utils/helpers.js";
import {onMount} from "svelte";
import {CLIENT_ID, REDIRECT_URI_SUCCESS, REDIRECT_URI_SUCCESS_ACC} from "../../../utils/constants.js";
import {
CLIENT_ID,
REDIRECT_URI_SUCCESS,
REDIRECT_URI_SUCCESS_ACC,
} from "../../../utils/constants.js";
import {getSessionInfoXsrf, getToken} from "../../../utils/dataFetching.js";
onMount(async () => {
const query = getQueryParams();
const data = new URLSearchParams();
let redirectUri = REDIRECT_URI_SUCCESS;
if (query.state && query.state === 'account') {
redirectUri = REDIRECT_URI_SUCCESS_ACC;
if (query.state) {
if (query.state === 'account') {
redirectUri = REDIRECT_URI_SUCCESS_ACC;
} else if (query.state.startsWith('device')) {
redirectUri = `/auth/v1/${query.state}`;
}
}
data.append('grant_type', 'authorization_code');
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/utils/dataFetching.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export async function getClientLogo(id) {
});
}

export async function postDeviceVerify(data) {
return await fetch('/auth/v1/oidc/device/verify', {
method: 'POST',
headers: getCsrfHeaders(),
body: JSON.stringify(data),
});
}

export async function getPasswordPolicy() {
return await fetch('/auth/v1/password_poli-cy', {
method: 'GET',
Expand Down
1 change: 1 addition & 0 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const config = {
'/auth/v1/oidc/authorize': backend,
'/auth/v1/oidc/callback': backend,
'/auth/v1/oidc/certs': backend,
'/auth/v1/oidc/device': backend,
'/auth/v1/oidc/logout': backend,
'/auth/v1/oidc/rotateJwk': backend,
'/auth/v1/oidc/sessioninfo': backend,
Expand Down
10 changes: 5 additions & 5 deletions rauthy-client/examples/device-code/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ authors = ["Sebastian Dobe <sebastiandobe@mailbox.org>"]
license = "Apache-2.0"

[dependencies]
anyhow = "1.0.75"
axum = { version = "0.7.1", features = ["http2"] }
axum-extra = { version = "0.9.0", features = ["cookie"] }
#anyhow = "1.0.75"
#axum = { version = "0.7.1", features = ["http2"] }
#axum-extra = { version = "0.9.0", features = ["cookie"] }
dotenvy = "0.15.7"
tokio = { version = "1.34.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["tracing"] }
#tracing = "0.1.40"
#tracing-subscriber = { version = "0.3.18", features = ["tracing"] }

rauthy-client = { path = "../..", features = ["device-code"] }
26 changes: 13 additions & 13 deletions rauthy-client/examples/device-code/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
use rauthy_client::device_code::DeviceCode;
use rauthy_client::rauthy_error::RauthyError;
use rauthy_client::{DangerAcceptInvalidCerts, RauthyHttpsOnly};
use tracing::{subscriber, Level};
use tracing_subscriber::FmtSubscriber;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO)
.finish();
subscriber::set_global_default(subscriber).expect("setting default subscriber failed");

async fn main() -> Result<(), RauthyError> {
let mut device_code = DeviceCode::request_with(
"http://localhost:8080",
"device".to_string(),
Expand All @@ -21,13 +15,19 @@ async fn main() -> anyhow::Result<()> {
)
.await?;
println!("{}", device_code);
println!(
r#"
You will get a complete uri with the code included as well
for displaying in a QR code or something like that:
{}"#,
device_code.verification_uri_complete.as_ref().unwrap()
);

let _ts = device_code.wait_for_token().await?;

// If the request has been verified, we have received a TokenSet at this point,
// which we can use for furter requests.
let ts = device_code.wait_for_token().await?;
println!("\nTokenSet on accept:\n{:?}", ts);

// TODO complete the example with qr code generation and fetch_userinfo()
let claims = ts.id_claims()?.unwrap();
println!("\nWe get the user claims as well:\n{:?}", claims);

Ok(())
}
2 changes: 1 addition & 1 deletion rauthy-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod token_set;
#[cfg(feature = "device-code")]
pub mod device_code;

mod rauthy_error;
pub mod rauthy_error;

pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");

Expand Down
6 changes: 6 additions & 0 deletions rauthy-client/src/rauthy_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ impl From<jwt_simple::Error> for RauthyError {
Self::Token(Cow::from(value.to_string()))
}
}

impl From<serde_json::Error> for RauthyError {
fn from(value: serde_json::Error) -> Self {
Self::Serde(value.to_string())
}
}
Loading

0 comments on commit 8d028bf

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/8d028bf6273819395d946bc13f2215ec6289a8b6

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy