Skip to content

Commit

Permalink
OIDC implementation (#39)
Browse files Browse the repository at this point in the history
Implement OID authentication
  • Loading branch information
ybizeul authored Sep 4, 2024
1 parent 4d65960 commit 838770a
Show file tree
Hide file tree
Showing 33 changed files with 709 additions and 263 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"program": "${workspaceFolder}/hupload",
"env": {
"JWT_SECRET": "9e1fada26b20ddc5ce812cafb8d2cada"
}
},
},
{
"name": "Launch hupload (Demo)",
Expand Down
31 changes: 24 additions & 7 deletions html/src/APIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,20 @@ export class APIClient {
console.log("logout")
return
}
login(path: string, user : string, password : string) {
return new Promise<AxiosResponse|APIServerError>((resolve, reject) => {
this.request({

login(path: string, user? : string, password? : string) {
return new Promise<unknown|APIServerError>((resolve, reject) => {
axios({
url: this.baseURL + path,
method: 'POST',
auth: {
maxRedirects: 0,
auth: (user && password)?{
username: user,
password: password
}
}:undefined
})
.then((result) => {
resolve(result)
resolve(result.data)
})
.catch (e => {
reject(e)
Expand Down Expand Up @@ -296,6 +297,11 @@ export class APIClient {
}
}

export interface Auth {
showLoginForm: boolean
loginUrl: string
}

class HuploadClient extends APIClient {
constructor() {
super('/api/v1')
Expand All @@ -309,6 +315,17 @@ class HuploadClient extends APIClient {
document.cookie = "X-Token" +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
document.cookie = "X-Token-Refresh" +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
auth() {
return new Promise<unknown|APIServerError>((resolve, reject) => {
this.request({
url: '/auth',
})
.then((result) => {
resolve(result)
})
.catch(reject)
})
}
}

export const H = new HuploadClient()
21 changes: 10 additions & 11 deletions html/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@ import { useEffect, useState } from "react";
import { Container, MantineProvider } from "@mantine/core";
import { BrowserRouter, Route, Routes } from "react-router-dom";

import { H } from "./APIClient";
//import { H } from "./APIClient";
import { SharePage, Login, SharesPage } from "@/Pages";

import { LoggedInContext } from "@/LoggedInContext";
import { LoggedInContext, LoggedIn } from "@/LoggedInContext";
import { VersionComponent } from "@/Components";
import { Haffix } from "./Components/Haffix";
import { H } from "./APIClient";
//import { AxiosResponse } from "axios";


// Logged in user is passed to the context
interface LoggedIn {
user: string
}

export default function App() {
// Component state
const [loggedIn, setLoggedIn ] = useState<string|null>(null)
const [loggedIn, setLoggedIn ] = useState<LoggedIn|null>(null)

// Check with server current logged in state
// This is typically executed once when Hupload is loaded
// State is updated later on login page or logout button
useEffect(() => {
H.post('/login').then((r) => {
H.login('/login').then((r) => {
const l = r as LoggedIn
setLoggedIn(l.user)
setLoggedIn(l)
})
.catch((e) => {
setLoggedIn(null)
Expand All @@ -40,9 +39,9 @@ export default function App() {
<Container flex={1} size="sm" w="100%" pt="md">
<BrowserRouter>
<LoggedInContext.Provider value={{loggedIn,setLoggedIn}}>
<Routes>
<Routes>
<Route path="/" element={<Login />}/>
<Route path="/shares" element={<>{loggedIn&&<Haffix/>}<SharesPage owner={loggedIn}/></>} />
<Route path="/shares" element={<>{loggedIn&&<Haffix/>}<SharesPage owner={loggedIn?(loggedIn.user):null}/></>} />
<Route path=":share" element={<>{loggedIn&&<Haffix/>}<SharePage /></>} />
</Routes>
</LoggedInContext.Provider>
Expand Down
5 changes: 4 additions & 1 deletion html/src/Components/Haffix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { ActionIcon, Affix, Tooltip } from "@mantine/core";
import { IconArrowLeft, IconLogout } from "@tabler/icons-react";
import { Link, useMatch } from "react-router-dom";
import { H } from "@/APIClient";
import { useLoggedInContext } from "@/LoggedInContext";

export function Haffix() {
const location = useMatch('/:share')
const { setLoggedIn } = useLoggedInContext()

return(
<>
<Affix position={{ bottom: 50 }} w="100%" ta="center">
<Tooltip withArrow arrowOffset={10} arrowSize={4} label="Shares">
<ActionIcon size="lg" mr="lg" disabled={location?.params.share == 'shares'} component={Link} to="/shares" color="blue" radius="xl"><IconArrowLeft style={{ width: '70%', height: '70%' }} /></ActionIcon>
</Tooltip>
<Tooltip withArrow arrowOffset={10} arrowSize={4} label="Logout">
<ActionIcon size="lg" variant="light" color="red" radius="xl" onClick={() => { H.logoutNow(); window.location.href='/'}}><IconLogout style={{ width: '70%', height: '70%' }} /></ActionIcon>
<ActionIcon size="lg" variant="light" color="red" radius="xl" onClick={() => { setLoggedIn(null);H.logoutNow(); window.location.href='/'}}><IconLogout style={{ width: '70%', height: '70%' }} /></ActionIcon>
</Tooltip>
</Affix>
</>
Expand Down
10 changes: 8 additions & 2 deletions html/src/LoggedInContext.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { createContext, useContext } from "react";

// Logged in user is passed to the context
export interface LoggedIn {
user: string
loginPage: string
}

interface LoggedInContextValue {
loggedIn: string | null;
setLoggedIn: React.Dispatch<React.SetStateAction<string | null>>;
loggedIn: LoggedIn | null;
setLoggedIn: React.Dispatch<React.SetStateAction<LoggedIn | null>>;
}

export const LoggedInContext = createContext<LoggedInContextValue|undefined>(undefined);
Expand Down
23 changes: 20 additions & 3 deletions html/src/Pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Alert, Button, Container, FocusTrap, Paper, PasswordInput, TextInput } from "@mantine/core";
import { useState } from "react";
import { APIServerError, H } from "../APIClient";
import { useEffect, useState } from "react";
import { APIServerError, Auth, H } from "../APIClient";

import { IconExclamationCircle } from "@tabler/icons-react";
import { useNavigate } from "react-router-dom";
import { useLoggedInContext } from "../LoggedInContext";


export function Login() {
// Initialize States
const [username, setUsername] = useState<undefined|string>("")
const [password, setPassword] = useState<undefined|string>("")
const [error, setError] = useState<APIServerError|undefined>()
const [showLoginForm, setShowLoginForm] = useState<boolean|undefined>(undefined)

// Initialize hooks
const navigate = useNavigate();
Expand All @@ -25,7 +27,7 @@ export function Login() {
setError(undefined)
navigate("/shares")
if (setLoggedIn !== null) {
setLoggedIn(username)
setLoggedIn({user: username, loginPage: '/login'})
}
})
.catch(e => {
Expand All @@ -34,6 +36,21 @@ export function Login() {
}
}

useEffect(() => {
H.auth()
.then((r) => {
const resp = r as Auth
setShowLoginForm(resp.showLoginForm)
if (resp.loginUrl !== document.location.pathname) {
window.location.href = resp.loginUrl
}
})
},[navigate])

if (showLoginForm !== true) {
return
}

return (
<Container size={420} my="10%">
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
Expand Down
13 changes: 6 additions & 7 deletions html/src/Pages/SharesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IconChevronDown, IconMoodSad } from "@tabler/icons-react";
import { AxiosError } from "axios";
import { ShareEditor } from "@/Components/ShareEditor";
import { useMediaQuery } from "@mantine/hooks";

import classes from './SharesPage.module.css';

export function SharesPage(props: {owner: string|null}) {
Expand Down Expand Up @@ -48,20 +49,18 @@ export function SharesPage(props: {owner: string|null}) {
})
.catch((e) => {
console.log(e)
setError(e)
if (e.response?.status === 401) {
navigate('/')
return
}
})
},[])
},[navigate])

useEffect(() => {
updateShares()
},[updateShares])

if (error) {
if (error.response?.status === 401) {
navigate('/')
return
}

return (
<Center h="100vh">
<Stack align="center" pb="10em">
Expand Down
3 changes: 3 additions & 0 deletions html/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default defineConfig({
proxy: {
'/api': 'http://127.0.0.1:8080/',
'/d/': 'http://127.0.0.1:8080/',
'/login': 'http://127.0.0.1:8080/',
'/oidc': 'http://127.0.0.1:8080/',
'/auth': 'http://127.0.0.1:8080/',
}
},
})
3 changes: 2 additions & 1 deletion hupload/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
admin-ui
__*
local
local
hupload
5 changes: 5 additions & 0 deletions hupload/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.27.30
github.com/aws/aws-sdk-go-v2/credentials v1.17.29
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1
github.com/coreos/go-oidc v2.2.1+incompatible
golang.org/x/crypto v0.25.0
golang.org/x/oauth2 v0.21.0
)

require (
Expand All @@ -30,4 +32,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/stretchr/testify v1.8.2 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)
24 changes: 24 additions & 0 deletions hupload/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,35 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wI
github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0=
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 838770a

Please sign in to comment.