diff --git a/pkg/views/bun.lockb b/pkg/views/bun.lockb
index 20e87a6..b20726d 100755
Binary files a/pkg/views/bun.lockb and b/pkg/views/bun.lockb differ
diff --git a/pkg/views/src/main.tsx b/pkg/views/src/main.tsx
index 942fdfb..e65d710 100644
--- a/pkg/views/src/main.tsx
+++ b/pkg/views/src/main.tsx
@@ -16,6 +16,7 @@ import AppShell from "@/components/AppShell.tsx";
import LandingPage from "@/pages/landing.tsx";
import SignUpPage from "@/pages/auth/sign-up.tsx";
import SignInPage from "@/pages/auth/sign-in.tsx";
+import OauthConnectPage from "@/pages/auth/connect.tsx";
import DashboardPage from "@/pages/users/dashboard.tsx";
import ErrorBoundary from "@/error.tsx";
import AppLoader from "@/components/AppLoader.tsx";
@@ -25,6 +26,8 @@ import PersonalizePage from "@/pages/users/personalize.tsx";
import SecurityPage from "@/pages/users/security.tsx";
import { UserinfoProvider } from "@/stores/userinfo.tsx";
import { WellKnownProvider } from "@/stores/wellKnown.tsx";
+import AuthLayout from "@/pages/auth/layout.tsx";
+import AuthGuard from "@/pages/guard.tsx";
declare const __GARFISH_EXPORTS__: {
provider: Object;
@@ -45,19 +48,33 @@ const router = createBrowserRouter([
children: [
{ path: "/", element: },
{
- path: "/users",
- element: ,
+ path: "/",
+ element: ,
children: [
- { path: "/users", element: },
- { path: "/users/notifications", element: },
- { path: "/users/personalize", element: },
- { path: "/users/security", element: }
+ {
+ path: "/users",
+ element: ,
+ children: [
+ { path: "/users", element: },
+ { path: "/users/notifications", element: },
+ { path: "/users/personalize", element: },
+ { path: "/users/security", element: }
+ ]
+ }
]
}
]
},
- { path: "/auth/sign-up", element: , errorElement: },
- { path: "/auth/sign-in", element: , errorElement: }
+ {
+ path: "/auth",
+ element: ,
+ errorElement: ,
+ children: [
+ { path: "/auth/sign-up", element: , errorElement: },
+ { path: "/auth/sign-in", element: , errorElement: },
+ { path: "/auth/o/connect", element: , errorElement: }
+ ]
+ }
]);
const element = (
diff --git a/pkg/views/src/pages/auth/connect.tsx b/pkg/views/src/pages/auth/connect.tsx
new file mode 100644
index 0000000..567a3ed
--- /dev/null
+++ b/pkg/views/src/pages/auth/connect.tsx
@@ -0,0 +1,182 @@
+import { useEffect, useState } from "react";
+import {
+ Alert,
+ Avatar,
+ Box,
+ Button,
+ Card,
+ CardContent,
+ Collapse,
+ Grid,
+ LinearProgress,
+ Typography
+} from "@mui/material";
+import { request } from "@/scripts/request.ts";
+import { useUserinfo } from "@/stores/userinfo.tsx";
+import { useSearchParams } from "react-router-dom";
+import OutletIcon from "@mui/icons-material/Outlet";
+import WhatshotIcon from "@mui/icons-material/Whatshot";
+
+export default function OauthConnectPage() {
+ const { getAtk } = useUserinfo();
+
+ const [panel, setPanel] = useState(0);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const [client, setClient] = useState(null);
+
+ const [searchParams] = useSearchParams();
+
+ async function preconnect() {
+ const res = await request(`/api/auth/o/connect${location.search}`, {
+ headers: { "Authorization": `Bearer ${getAtk()}` }
+ });
+
+ if (res.status !== 200) {
+ setError(await res.text());
+ } else {
+ const data = await res.json();
+
+ if (data["session"]) {
+ setPanel(1);
+ redirect(data["session"]);
+ } else {
+ setClient(data["client"]);
+ setLoading(false);
+ }
+ }
+ }
+
+ useEffect(() => {
+ preconnect().then(() => console.log("Fetched metadata"));
+ }, []);
+
+ function decline() {
+ if (window.history.length > 0) {
+ window.history.back();
+ } else {
+ window.close();
+ }
+ }
+
+ async function approve() {
+ setLoading(true);
+
+ const res = await request("/api/auth/o/connect?" + new URLSearchParams({
+ client_id: searchParams.get("client_id") as string,
+ redirect_uri: encodeURIComponent(searchParams.get("redirect_uri") as string),
+ response_type: "code",
+ scope: searchParams.get("scope") as string
+ }), {
+ method: "POST",
+ headers: { "Authorization": `Bearer ${getAtk()}` }
+ });
+
+ if (res.status !== 200) {
+ setError(await res.text());
+ setLoading(false);
+ } else {
+ const data = await res.json();
+ setPanel(1);
+ setTimeout(() => redirect(data["session"]), 1850);
+ }
+ }
+
+ function redirect(session: any) {
+ const url = `${searchParams.get("redirect_uri")}?code=${session["grant_token"]}&state=${searchParams.get("state")}`;
+ window.open(url, "_self");
+ }
+
+ const elements = [
+ (
+ <>
+
+
+
+
+ Sign in to {client?.name}
+
+
+
+
+ About this app
+ {client?.description}
+
+
+ Make you trust this app
+
+ After you click Approve button, you will share your basic personal information to this application
+ developer. Some of them will leak your data. Think twice.
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ (
+ <>
+
+
+
+
+ Authorized
+
+
+
+
+ Now Redirecting...
+ Hold on a second, we are going to redirect you to the target.
+
+
+
+ >
+ )
+ ];
+
+ return (
+ <>
+ {error && {error}}
+
+
+
+
+
+
+
+ {elements[panel]}
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/pkg/views/src/pages/auth/layout.tsx b/pkg/views/src/pages/auth/layout.tsx
new file mode 100644
index 0000000..f9dec4c
--- /dev/null
+++ b/pkg/views/src/pages/auth/layout.tsx
@@ -0,0 +1,12 @@
+import { Box } from "@mui/material";
+import { Outlet } from "react-router-dom";
+
+export default function AuthLayout() {
+ return (
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/pkg/views/src/pages/auth/sign-in.tsx b/pkg/views/src/pages/auth/sign-in.tsx
index 2173d10..22a62a7 100644
--- a/pkg/views/src/pages/auth/sign-in.tsx
+++ b/pkg/views/src/pages/auth/sign-in.tsx
@@ -277,51 +277,55 @@ export default function SignInPage() {
}
return (
-
-
- {error && {error}}
+ <>
+ {error && {error}}
-
-
-
-
+
+
+ You need sign in before take an action. After that, we will take you back to your work.
+
+
-
- {elements[panel]}
-
+
+
+
+
-
-
-
-
- Risk {challenge?.risk_level}
- Progress {challenge?.progress}/{challenge?.requirements}
-
-
-
-
-
-
+
+ {elements[panel]}
+
-
-
-
- Haven't an account? Sign up!
-
-
+
+
+
+
+ Risk {challenge?.risk_level}
+ Progress {challenge?.progress}/{challenge?.requirements}
+
+
+
+
+
+
+
+
+
+
+ Haven't an account? Sign up!
+
-
-
+
+ >
);
}
\ No newline at end of file
diff --git a/pkg/views/src/pages/auth/sign-up.tsx b/pkg/views/src/pages/auth/sign-up.tsx
index ba3dc8b..0af8031 100644
--- a/pkg/views/src/pages/auth/sign-up.tsx
+++ b/pkg/views/src/pages/auth/sign-up.tsx
@@ -166,35 +166,33 @@ export default function SignUpPage() {
];
return (
-
-
- {error && {error}}
+ <>
+ {error && {error}}
-
-
-
-
+
+
+
+
-
- {!done ? elements[0] : elements[1]}
-
-
+
+ {!done ? elements[0] : elements[1]}
+
+
-
-
-
- Already have an account? Sign in!
-
-
+
+
+
+ Already have an account? Sign in!
+
-
-
+
+ >
);
}
\ No newline at end of file
diff --git a/pkg/views/src/pages/guard.tsx b/pkg/views/src/pages/guard.tsx
new file mode 100644
index 0000000..ce39a4c
--- /dev/null
+++ b/pkg/views/src/pages/guard.tsx
@@ -0,0 +1,29 @@
+import { useEffect } from "react";
+import { Box, CircularProgress } from "@mui/material";
+import { Outlet, useLocation, useNavigate } from "react-router-dom";
+import { useUserinfo } from "@/stores/userinfo.tsx";
+
+export default function AuthGuard() {
+ const { userinfo } = useUserinfo();
+
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ useEffect(() => {
+ console.log(userinfo)
+ if (userinfo?.isReady) {
+ if (!userinfo?.isLoggedIn) {
+ const callback = location.pathname + location.search;
+ navigate({ pathname: "/auth/sign-in", search: `redirect_uri=${callback}` });
+ }
+ }
+ }, [userinfo]);
+
+ return !userinfo?.isReady ? (
+
+
+
+
+
+ ) : ;
+}
\ No newline at end of file
diff --git a/pkg/views/src/stores/userinfo.tsx b/pkg/views/src/stores/userinfo.tsx
index e3400f7..4f70622 100644
--- a/pkg/views/src/stores/userinfo.tsx
+++ b/pkg/views/src/stores/userinfo.tsx
@@ -3,15 +3,17 @@ import { request } from "../scripts/request.ts";
import { createContext, useContext, useState } from "react";
export interface Userinfo {
+ isReady: boolean,
isLoggedIn: boolean,
displayName: string,
data: any,
}
const defaultUserinfo: Userinfo = {
+ isReady: false,
isLoggedIn: false,
displayName: "Citizen",
- data: null,
+ data: null
};
const UserinfoContext = createContext({ userinfo: defaultUserinfo });
@@ -28,10 +30,15 @@ export function UserinfoProvider(props: any) {
}
async function readProfiles() {
- if (!checkLoggedIn()) return;
+ if (!checkLoggedIn()) {
+ setUserinfo((data) => {
+ data.isReady = true;
+ return data;
+ });
+ }
const res = await request("/api/users/me", {
- credentials: "include"
+ headers: { "Authorization": `Bearer ${getAtk()}` }
});
if (res.status !== 200) {
@@ -42,6 +49,7 @@ export function UserinfoProvider(props: any) {
const data = await res.json();
setUserinfo({
+ isReady: true,
isLoggedIn: true,
displayName: data["nick"],
data: data