feat(authorization): e2e working authorization
This commit is contained in:
Binary file not shown.
@ -23,6 +23,7 @@
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"clsx": "^2.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"solid-js": "^1.9.3",
|
||||
"solid-motionone": "^1.0.3",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
|
@ -7,7 +7,6 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Emitter;
|
||||
use tauri::TitleBarStyle;
|
||||
use tauri::{WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
struct WatcherState {
|
||||
@ -116,7 +115,7 @@ pub fn run() {
|
||||
.setup(|app| {
|
||||
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.inner_size(480.0, 360.0)
|
||||
.hidden_title(true)
|
||||
// .hidden_title(true)
|
||||
.resizable(true);
|
||||
// set transparent title bar only when building for macOS
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -2,6 +2,8 @@ import { Button } from "@kobalte/core/button";
|
||||
import { TextField } from "@kobalte/core/text-field";
|
||||
import { createSignal, Show, type Component } from "solid-js";
|
||||
import { postCode, postLogin } from "./network";
|
||||
import { isTokenValid } from "./ProtectedRoute";
|
||||
import { Navigate } from "@solidjs/router";
|
||||
|
||||
export const Login: Component = () => {
|
||||
let form: HTMLFormElement | undefined;
|
||||
@ -35,19 +37,23 @@ export const Login: Component = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const isAuthorized = isTokenValid();
|
||||
|
||||
return (
|
||||
<form ref={form} onSubmit={onSubmit}>
|
||||
<TextField name="email">
|
||||
<TextField.Label>Email</TextField.Label>
|
||||
<TextField.Input />
|
||||
</TextField>
|
||||
<Show when={submitted()}>
|
||||
<TextField name="code">
|
||||
<TextField.Label>Code</TextField.Label>
|
||||
<Show when={!isAuthorized} fallback={<Navigate href="/" />}>
|
||||
<form ref={form} onSubmit={onSubmit}>
|
||||
<TextField name="email">
|
||||
<TextField.Label>Email</TextField.Label>
|
||||
<TextField.Input />
|
||||
</TextField>
|
||||
</Show>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
<Show when={submitted()}>
|
||||
<TextField name="code">
|
||||
<TextField.Label>Code</TextField.Label>
|
||||
<TextField.Input />
|
||||
</TextField>
|
||||
</Show>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
30
frontend/src/ProtectedRoute.tsx
Normal file
30
frontend/src/ProtectedRoute.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { type Component, type JSX, Show } from "solid-js";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { Navigate } from "@solidjs/router";
|
||||
|
||||
export const isTokenValid = (): boolean => {
|
||||
const token = localStorage.getItem("access");
|
||||
|
||||
if (token == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
jwtDecode(token);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const ProtectedRoute: Component<{ children?: JSX.Element }> = (
|
||||
props,
|
||||
) => {
|
||||
const isValid = isTokenValid();
|
||||
|
||||
return (
|
||||
<Show when={isValid} fallback={<Navigate href="/login" />}>
|
||||
{props.children}
|
||||
</Show>
|
||||
);
|
||||
};
|
@ -5,13 +5,17 @@ import "./index.css";
|
||||
import { Route, Router } from "@solidjs/router";
|
||||
import { ImagePage } from "./ImagePage";
|
||||
import { Login } from "./Login";
|
||||
import { ProtectedRoute } from "./ProtectedRoute";
|
||||
|
||||
render(
|
||||
() => (
|
||||
<Router>
|
||||
<Route path="/" component={App} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/image/:imageId" component={ImagePage} />
|
||||
|
||||
<Route path="/" component={ProtectedRoute}>
|
||||
<Route path="/" component={App} />
|
||||
<Route path="/image/:imageId" component={ImagePage} />
|
||||
</Route>
|
||||
</Router>
|
||||
),
|
||||
document.getElementById("root") as HTMLElement,
|
||||
|
@ -19,12 +19,24 @@ type BaseRequestParams = Partial<{
|
||||
|
||||
const getBaseRequest = ({ path, body, method }: BaseRequestParams): Request => {
|
||||
return new Request(`http://localhost:3040/${path}`, {
|
||||
headers: { userId: "1db09f34-b155-4bf2-b606-dda25365fc89" },
|
||||
body,
|
||||
method,
|
||||
});
|
||||
};
|
||||
|
||||
const getBaseAuthorizedRequest = ({
|
||||
path,
|
||||
body,
|
||||
method,
|
||||
}: BaseRequestParams): Request => {
|
||||
return new Request(`http://localhost:3040/${path}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("access")?.toString()}`,
|
||||
},
|
||||
body,
|
||||
method,
|
||||
});
|
||||
};
|
||||
const sendImageResponseValidator = strictObject({
|
||||
ID: pipe(string(), uuid()),
|
||||
ImageID: pipe(string(), uuid()),
|
||||
@ -35,7 +47,7 @@ export const sendImage = async (
|
||||
imageName: string,
|
||||
base64Image: string,
|
||||
): Promise<InferOutput<typeof sendImageResponseValidator>> => {
|
||||
const request = getBaseRequest({
|
||||
const request = getBaseAuthorizedRequest({
|
||||
path: `image/${imageName}`,
|
||||
body: base64Image,
|
||||
method: "POST",
|
||||
@ -107,7 +119,7 @@ const getUserImagesResponseValidator = array(dataTypeValidator);
|
||||
export type UserImage = InferOutput<typeof dataTypeValidator>;
|
||||
|
||||
export const getUserImages = async (): Promise<UserImage[]> => {
|
||||
const request = getBaseRequest({ path: "image" });
|
||||
const request = getBaseAuthorizedRequest({ path: "image" });
|
||||
|
||||
const res = await fetch(request).then((res) => res.json());
|
||||
|
||||
|
Reference in New Issue
Block a user