Compare commits
10 Commits
d64c8479f8
...
428dc50aa1
Author | SHA1 | Date | |
---|---|---|---|
428dc50aa1 | |||
2663264f50 | |||
ffba961d72 | |||
5a939c0771 | |||
87916f96fd | |||
c6c03e9cb6 | |||
6fd7171450 | |||
ae07d2d3d9 | |||
65545a0d71 | |||
d423d9ba62 |
49
internal/oauth/routes.go
Normal file
49
internal/oauth/routes.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.local/admin/hspguard/internal/web"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuthHandler struct{}
|
||||||
|
|
||||||
|
func NewOAuthHandler() *OAuthHandler {
|
||||||
|
return &OAuthHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OAuthHandler) RegisterRoutes(r chi.Router) {
|
||||||
|
r.Get("/oauth/authorize", h.authorizeEndpoint)
|
||||||
|
r.Get("/oauth/token", h.tokenEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("[OAUTH] New request to token endpoint")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("OK"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OAuthHandler) authorizeEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("[OAUTH] New request to authorize endpoint")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("OK"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *OAuthHandler) Metadata(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type Response struct {
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
AuthEndpoint string `json:"authorization_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
|
||||||
|
if err := encoder.Encode(Response{
|
||||||
|
TokenEndpoint: "http://192.168.178.21:3001/api/v1/oauth/token",
|
||||||
|
AuthEndpoint: "http://192.168.178.21:5173/authorize",
|
||||||
|
}); err != nil {
|
||||||
|
web.Error(w, "failed to encode response", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,10 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|||||||
import IndexPage from "./pages/Index";
|
import IndexPage from "./pages/Index";
|
||||||
import LoginPage from "./pages/Login";
|
import LoginPage from "./pages/Login";
|
||||||
import RegisterPage from "./pages/Register";
|
import RegisterPage from "./pages/Register";
|
||||||
import { useDbContext } from "./context/db/db";
|
import { useDbContext } from "./context/db";
|
||||||
import { openDB } from "idb";
|
import { openDB } from "idb";
|
||||||
import AgreementPage from "./pages/Agreement";
|
import AgreementPage from "./pages/Agreement";
|
||||||
|
import OAuthAuthorizePage from "./pages/OAuthAuthorize";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -25,6 +26,10 @@ const router = createBrowserRouter([
|
|||||||
path: "/register",
|
path: "/register",
|
||||||
element: <RegisterPage />,
|
element: <RegisterPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/authorize",
|
||||||
|
element: <OAuthAuthorizePage />,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useState, type FC, type ReactNode } from "react";
|
import { useCallback, useState, type FC, type ReactNode } from "react";
|
||||||
import { DbContext } from "./db";
|
import { DbContext } from ".";
|
||||||
import type { IDBPDatabase } from "idb";
|
import type { IDBPDatabase } from "idb";
|
||||||
|
|
||||||
interface IDBProvider {
|
interface IDBProvider {
|
||||||
|
33
web/src/context/oauth/index.ts
Normal file
33
web/src/context/oauth/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
interface OAuthContextValues {
|
||||||
|
active: boolean;
|
||||||
|
clientID: string;
|
||||||
|
redirectURI: string;
|
||||||
|
scope: string[];
|
||||||
|
state: string;
|
||||||
|
nonce: string;
|
||||||
|
setActive: (state: boolean) => void;
|
||||||
|
setClientID: (id: string) => void;
|
||||||
|
setRedirectURI: (uri: string) => void;
|
||||||
|
setScope: (scopes: string[]) => void;
|
||||||
|
setState: (state: string) => void;
|
||||||
|
setNonce: (nonce: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OAuthContext = createContext<OAuthContextValues>({
|
||||||
|
active: false,
|
||||||
|
clientID: "",
|
||||||
|
redirectURI: "",
|
||||||
|
scope: [],
|
||||||
|
state: "",
|
||||||
|
nonce: "",
|
||||||
|
setActive: () => {},
|
||||||
|
setClientID: () => {},
|
||||||
|
setRedirectURI: () => {},
|
||||||
|
setScope: () => {},
|
||||||
|
setState: () => {},
|
||||||
|
setNonce: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useOAuthContext = () => useContext(OAuthContext);
|
36
web/src/context/oauth/provider.tsx
Normal file
36
web/src/context/oauth/provider.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useState, type FC, type ReactNode } from "react";
|
||||||
|
import { OAuthContext } from ".";
|
||||||
|
|
||||||
|
interface IOAuthProvider {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OAuthProvider: FC<IOAuthProvider> = ({ children }) => {
|
||||||
|
const [active, setActive] = useState(false);
|
||||||
|
const [clientID, setClientID] = useState("");
|
||||||
|
const [redirectURI, setRedirectURI] = useState("");
|
||||||
|
const [scope, setScope] = useState<string[]>([]);
|
||||||
|
const [state, setState] = useState("");
|
||||||
|
const [nonce, setNonce] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OAuthContext.Provider
|
||||||
|
value={{
|
||||||
|
active,
|
||||||
|
clientID,
|
||||||
|
redirectURI,
|
||||||
|
scope,
|
||||||
|
state,
|
||||||
|
nonce,
|
||||||
|
setActive,
|
||||||
|
setClientID,
|
||||||
|
setRedirectURI,
|
||||||
|
setScope,
|
||||||
|
setState,
|
||||||
|
setNonce,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</OAuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,8 @@
|
|||||||
import { useDbContext } from "@/context/db/db";
|
import { useDbContext } from "@/context/db";
|
||||||
import { type LocalAccount, useAccountRepo } from "@/repository/account";
|
import { type LocalAccount, useAccountRepo } from "@/repository/account";
|
||||||
import { CirclePlus, User } from "lucide-react";
|
import { CirclePlus, User } from "lucide-react";
|
||||||
import { useEffect, useState, type FC } from "react";
|
import { useEffect, useState, type FC } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const AccountList: FC = () => {
|
const AccountList: FC = () => {
|
||||||
const [accounts, setAccounts] = useState<LocalAccount[]>([]);
|
const [accounts, setAccounts] = useState<LocalAccount[]>([]);
|
||||||
@ -72,6 +73,7 @@ const AccountList: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<Link to="/login">
|
||||||
<div className="flex flex-row items-center p-4 border-gray-200 dark:border-gray-700/65 border-b border-r-0 border-l-0 select-none cursor-pointer hover:bg-gray-50/50 dark:hover:bg-gray-800/10 transition-colors mb-0">
|
<div className="flex flex-row items-center p-4 border-gray-200 dark:border-gray-700/65 border-b border-r-0 border-l-0 select-none cursor-pointer hover:bg-gray-50/50 dark:hover:bg-gray-800/10 transition-colors mb-0">
|
||||||
<div>
|
<div>
|
||||||
<div className="rounded-full p-2 text-gray-900 dark:text-gray-200 mr-3">
|
<div className="rounded-full p-2 text-gray-900 dark:text-gray-200 mr-3">
|
||||||
@ -82,6 +84,7 @@ const AccountList: FC = () => {
|
|||||||
Add new account
|
Add new account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,11 +3,14 @@ import App from "./App";
|
|||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { DbProvider } from "./context/db/provider";
|
import { DbProvider } from "./context/db/provider";
|
||||||
|
import { OAuthProvider } from "./context/oauth/provider";
|
||||||
|
|
||||||
const root = document.getElementById("root")!;
|
const root = document.getElementById("root")!;
|
||||||
|
|
||||||
createRoot(root).render(
|
createRoot(root).render(
|
||||||
<DbProvider>
|
<DbProvider>
|
||||||
|
<OAuthProvider>
|
||||||
<App />
|
<App />
|
||||||
|
</OAuthProvider>
|
||||||
</DbProvider>
|
</DbProvider>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
const AgreementPage: FC = () => {
|
const AgreementPage: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(dark-overlay.jpg)]`}
|
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(/dark-overlay.jpg)]`}
|
||||||
>
|
>
|
||||||
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
||||||
<Card className="sm:w-[425px] sm:min-w-[425px] sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/65 backdrop-blur-md">
|
<Card className="sm:w-[425px] sm:min-w-[425px] sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/65 backdrop-blur-md">
|
||||||
|
@ -10,7 +10,7 @@ const IndexPage: FC = () => {
|
|||||||
// console.log(overlay);
|
// console.log(overlay);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(dark-overlay.jpg)]`}
|
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(/dark-overlay.jpg)]`}
|
||||||
// style={{ backgroundImage: `url(${overlay})` }}
|
// style={{ backgroundImage: `url(${overlay})` }}
|
||||||
>
|
>
|
||||||
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
||||||
|
@ -70,9 +70,7 @@ export default function LoginPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(/dark-overlay.jpg)]">
|
||||||
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(dark-overlay.jpg)]`}
|
|
||||||
>
|
|
||||||
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
||||||
<Card className="sm:w-96 sm:min-w-96 sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/70 backdrop-blur-md">
|
<Card className="sm:w-96 sm:min-w-96 sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/70 backdrop-blur-md">
|
||||||
<div className="flex flex-col items-center pt-16 sm:pt-0">
|
<div className="flex flex-col items-center pt-16 sm:pt-0">
|
||||||
|
74
web/src/pages/OAuthAuthorize/index.tsx
Normal file
74
web/src/pages/OAuthAuthorize/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { useOAuthContext } from "@/context/oauth";
|
||||||
|
import AccountList from "@/feature/AccountList";
|
||||||
|
import { useEffect, type FC } from "react";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
const OAuthAuthorizePage: FC = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const {
|
||||||
|
setActive,
|
||||||
|
setClientID,
|
||||||
|
setRedirectURI,
|
||||||
|
setScope,
|
||||||
|
setState,
|
||||||
|
setNonce,
|
||||||
|
} = useOAuthContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActive(true);
|
||||||
|
setClientID(searchParams.get("client_id") ?? "");
|
||||||
|
setRedirectURI(searchParams.get("redirect_uri") ?? "");
|
||||||
|
const scope = searchParams.get("scope") ?? "";
|
||||||
|
setScope(scope.split(" ").filter((s) => s.length > 0));
|
||||||
|
setState(searchParams.get("state") ?? "");
|
||||||
|
setNonce(searchParams.get("nonce") ?? "");
|
||||||
|
}, [
|
||||||
|
searchParams,
|
||||||
|
setActive,
|
||||||
|
setClientID,
|
||||||
|
setNonce,
|
||||||
|
setRedirectURI,
|
||||||
|
setScope,
|
||||||
|
setState,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(/dark-overlay.jpg)]`}
|
||||||
|
// style={{ backgroundImage: `url(${overlay})` }}
|
||||||
|
>
|
||||||
|
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
||||||
|
<Card className="sm:w-[700px] sm:min-w-[700px] sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/65 backdrop-blur-md">
|
||||||
|
<div className="flex sm:flex-row flex-col sm:items-stretch items-center pt-16 sm:pt-0">
|
||||||
|
<div className="flex flex-col items-center flex-1">
|
||||||
|
<img
|
||||||
|
src="/icon.png"
|
||||||
|
alt="icon"
|
||||||
|
className="w-16 h-16 mb-4 mt-2 sm:mt-6"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="px-4 sm:mt-4 mt-8">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 text-left w-full dark:text-gray-100">
|
||||||
|
Select Account
|
||||||
|
</h2>
|
||||||
|
<h4 className="text-base mb-3 text-gray-400 text-left dark:text-gray-300">
|
||||||
|
Choose one of the accounts below in order to proceed to home
|
||||||
|
lab services and tools.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <LogIn className="w-8 h-8 text-gray-700 mb-4" /> */}
|
||||||
|
<CardContent className="w-full space-y-4 flex-1">
|
||||||
|
<AccountList />
|
||||||
|
</CardContent>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OAuthAuthorizePage;
|
@ -74,7 +74,7 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(dark-overlay.jpg)]`}
|
className={`relative min-h-screen bg-cover bg-center bg-white dark:bg-black bg-[url(/overlay.jpg)] dark:bg-[url(/dark-overlay.jpg)]`}
|
||||||
>
|
>
|
||||||
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
<div className="relative z-10 flex items-center justify-center min-h-screen">
|
||||||
<Card className="sm:w-96 sm:min-w-96 sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/65 backdrop-blur-md">
|
<Card className="sm:w-96 sm:min-w-96 sm:max-w-96 sm:min-h-auto p-3 min-h-screen w-full min-w-full shadow-lg bg-white/65 dark:bg-black/65 backdrop-blur-md">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useDbContext } from "@/context/db/db";
|
import { useDbContext } from "@/context/db";
|
||||||
import { deriveDeviceKey, getDeviceId } from "@/util/deviceId";
|
import { deriveDeviceKey, getDeviceId } from "@/util/deviceId";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ export default defineConfig(({ mode }) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
allowedHosts: true,
|
allowedHosts: true,
|
||||||
|
host: "0.0.0.0",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
build: {
|
build: {
|
||||||
|
Reference in New Issue
Block a user