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