diff --git a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java index 7dcde8e..e6e126f 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/ArtifactController.java @@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.service.ArtifactService; @@ -28,6 +29,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/artifacts") @Tag(name = "artifact", description = "Operations on /api/artifacts") +@SecurityRequirement(name = "bearer") @Validated public class ArtifactController { diff --git a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java index ba9a11c..3037aa2 100644 --- a/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java +++ b/service/fling/src/main/java/net/friedl/fling/controller/FlingController.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.FlingDto; @@ -32,6 +33,7 @@ import net.friedl.fling.service.archive.ArchiveService; @RestController @RequestMapping("/api/fling") @Tag(name = "fling", description = "Operations on /api/fling") +@SecurityRequirement(name = "bearer") public class FlingController { private FlingService flingService; diff --git a/web/fling/src/App.jsx b/web/fling/src/App.jsx index d3dcb83..5cfcb31 100644 --- a/web/fling/src/App.jsx +++ b/web/fling/src/App.jsx @@ -3,7 +3,7 @@ import React from 'react'; import {Switch, Route, Redirect} from "react-router-dom"; -import request, {isOwner, isUser} from './util/request'; +import jwt from './util/jwt.js'; import Login from './components/admin/Login'; import FlingAdmin from './components/admin/FlingAdmin'; @@ -13,53 +13,64 @@ import FlingUser from './components/user/FlingUser'; import LandingPage from './components/LandingPage'; export default () => { - return ( - - + return ( + + - - - + + + - - + + - Not implemented - - ); + Not implemented + + ); } -// A wrapper for that redirects to the login -// screen if you're not yet authenticated. +/* + * A wrapper for that redirects to the login screen if no admin + * authentication token was found. + * + * Note that the token check is purely client-side. It provides no actual + * protection! It is hence possible to reach the admin site with some small + * amount of trickery. Without a valid token no meaningful actions are possible + * on the admin page though. + */ function OwnerRoute({ children, ...rest }) { - return ( - { - log.info(request.defaults); - if(isOwner()) { return children; } - else { return ; } - }} - /> - ); + log.info(`Routing request for ${rest['path']}`); + return ( + { + if (jwt.hasSubject("admin")) { return children; } + else { return ; } + }} + /> + ); } -// A wrapper for that redirects to the unlock -// screen if the fling is protected +/* A wrapper for that redirects to the unlock screen if no authorized token + * was found. + * + * Note that the token check is purely client-side. It provides no actual + * protection! It is hence possible to reach the target site with some small + * amount of trickery. Without a valid token, no meaningful actions are possible + * on the target page though - this must be checked server side. + */ function UserRoute({ children, ...rest }) { - return ( - { - log.info(request.defaults); - log.info(match); - log.info(location); - let x = {from: location, shareId: match.params.shareId}; + log.debug(`Routing request for ${rest['path']}`); + return ( + { + let state = {from: location, shareId: match.params.shareId}; + let authorized = jwt.hasSubject("admin") || (jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId'])); - if(isOwner()) { return children; } - else if(isUser(match.params.shareId)) { return children; } - else { return ; } - }} - /> - ); + if (authorized) { return children; } + else { return ; } + }} + /> + ); } diff --git a/web/fling/src/components/admin/FlingList.jsx b/web/fling/src/components/admin/FlingList.jsx index 3b501a6..99da35d 100644 --- a/web/fling/src/components/admin/FlingList.jsx +++ b/web/fling/src/components/admin/FlingList.jsx @@ -1,37 +1,34 @@ import log from 'loglevel'; import React, {useState, useEffect} from 'react'; -import {flingClient} from '../../util/flingclient'; +import {FlingClient} from '../../util/fc'; import FlingTile from './FlingTile'; export default function FlingList(props) { - const [flings, setFlings] = useState([]); - useEffect(() => { (async () => { - let flings = await flingClient.getFlings(); + const [flings, setFlings] = useState([]); + useEffect(() => { + let flingClient = new FlingClient(); + flingClient.getFlings() + .then(flings => { let newFlings = []; - for (let fling of flings) { - let flingTile = ; - newFlings.push(flingTile); + let flingTile = ; + newFlings.push(flingTile); } setFlings(newFlings); - })(); } , []); + }).catch(log.error); + }, []); - return( -
- {log.info(`Got active fling: ${props.activeFling}`)} -
-
My Flings
-
-
- {flings} -
-
- ); - - async function refreshFlingList() { - } + return( +
+ {log.info(`Got active fling: ${props.activeFling}`)} +
+
My Flings
+
+
+ {flings} +
+
+ ); } diff --git a/web/fling/src/components/admin/Login.jsx b/web/fling/src/components/admin/Login.jsx index b56f599..3599e22 100644 --- a/web/fling/src/components/admin/Login.jsx +++ b/web/fling/src/components/admin/Login.jsx @@ -51,8 +51,8 @@ export default function Login() { authClient.authenticateOwner(opt) .then(response => { log.info("Login successful"); - sessionStorage.setItem['token'] = response; - log.info("Returning back to", from); + sessionStorage.setItem('token', response); + log.debug("Returning back to", from); history.replace(from); }).catch(log.error); }; diff --git a/web/fling/src/util/fc.js b/web/fling/src/util/fc.js index b901f7a..11445d7 100644 --- a/web/fling/src/util/fc.js +++ b/web/fling/src/util/fc.js @@ -4,7 +4,7 @@ let clientConfig = (token) => { let config = new fc.ApiClient(); config.basePath = process.env.REACT_APP_API.replace(/\/+$/, ''); - token = token || sessionStorage.getItem['token']; + token = token || sessionStorage.getItem('token'); if(token) { config.authentications['bearer'].accessToken = token; } return config; diff --git a/web/fling/src/util/jwt.js b/web/fling/src/util/jwt.js new file mode 100644 index 0000000..696d13e --- /dev/null +++ b/web/fling/src/util/jwt.js @@ -0,0 +1,26 @@ +/** + * Utilities for working with JWT tokens + */ +import jwtDecode from 'jwt-decode'; + +let jwt = { + /** + * Check the session store token for an arbitrary claim + */ + hasClaim: function (name, value) { + if(!sessionStorage.getItem('token')) return false; + let tokenPayload = jwtDecode(sessionStorage.getItem('token')); + return tokenPayload[name] === value; + }, + + /** + * Check the session store token for a subject + */ + hasSubject: function (value) { + if(!sessionStorage.getItem('token')) return false; + let tokenPayload = jwtDecode(sessionStorage.getItem('token')); + return tokenPayload['sub'] === value; + } +}; + +export default jwt;