0.1 #1

Merged
armin merged 30 commits from 0.1 into master 2020-07-26 00:51:55 +00:00
7 changed files with 105 additions and 67 deletions
Showing only changes of commit bcfbf349cd - Show all commits

View file

@ -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.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse; 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 io.swagger.v3.oas.annotations.tags.Tag;
import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.ArtifactDto;
import net.friedl.fling.service.ArtifactService; import net.friedl.fling.service.ArtifactService;
@ -28,6 +29,7 @@ import net.friedl.fling.service.archive.ArchiveService;
@RestController @RestController
@RequestMapping("/api/artifacts") @RequestMapping("/api/artifacts")
@Tag(name = "artifact", description = "Operations on /api/artifacts") @Tag(name = "artifact", description = "Operations on /api/artifacts")
@SecurityRequirement(name = "bearer")
@Validated @Validated
public class ArtifactController { public class ArtifactController {

View file

@ -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.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; 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 io.swagger.v3.oas.annotations.tags.Tag;
import net.friedl.fling.model.dto.ArtifactDto; import net.friedl.fling.model.dto.ArtifactDto;
import net.friedl.fling.model.dto.FlingDto; import net.friedl.fling.model.dto.FlingDto;
@ -32,6 +33,7 @@ import net.friedl.fling.service.archive.ArchiveService;
@RestController @RestController
@RequestMapping("/api/fling") @RequestMapping("/api/fling")
@Tag(name = "fling", description = "Operations on /api/fling") @Tag(name = "fling", description = "Operations on /api/fling")
@SecurityRequirement(name = "bearer")
public class FlingController { public class FlingController {
private FlingService flingService; private FlingService flingService;

View file

@ -3,7 +3,7 @@ import React from 'react';
import {Switch, Route, Redirect} from "react-router-dom"; 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 Login from './components/admin/Login';
import FlingAdmin from './components/admin/FlingAdmin'; import FlingAdmin from './components/admin/FlingAdmin';
@ -29,36 +29,47 @@ export default () => {
); );
} }
// A wrapper for <Route> that redirects to the login /*
// screen if you're not yet authenticated. * A wrapper for <Route> 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 }) { function OwnerRoute({ children, ...rest }) {
log.info(`Routing request for ${rest['path']}`);
return ( return (
<Route <Route
{...rest} {...rest}
render={({ location }) => { render={({ location }) => {
log.info(request.defaults); if (jwt.hasSubject("admin")) { return children; }
if(isOwner()) { return children; }
else { return <Redirect to={{pathname: "/admin/login", state: {from: location}}} />; } else { return <Redirect to={{pathname: "/admin/login", state: {from: location}}} />; }
}} }}
/> />
); );
} }
// A wrapper for <Route> that redirects to the unlock /* A wrapper for <Route> that redirects to the unlock screen if no authorized token
// screen if the fling is protected * 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 }) { function UserRoute({ children, ...rest }) {
log.debug(`Routing request for ${rest['path']}`);
return ( return (
<Route <Route
{...rest} {...rest}
render={({ match, location }) => { render={({ match, location }) => {
log.info(request.defaults); let state = {from: location, shareId: match.params.shareId};
log.info(match); let authorized = jwt.hasSubject("admin") || (jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId']));
log.info(location);
let x = {from: location, shareId: match.params.shareId};
if(isOwner()) { return children; } if (authorized) { return children; }
else if(isUser(match.params.shareId)) { return children; } else { return <Redirect to={ {pathname: "/unlock", state: state} } />; }
else { return <Redirect to={ {pathname: "/unlock", state: x} } />; }
}} }}
/> />
); );

View file

@ -1,24 +1,24 @@
import log from 'loglevel'; import log from 'loglevel';
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import {flingClient} from '../../util/flingclient'; import {FlingClient} from '../../util/fc';
import FlingTile from './FlingTile'; import FlingTile from './FlingTile';
export default function FlingList(props) { export default function FlingList(props) {
const [flings, setFlings] = useState([]); const [flings, setFlings] = useState([]);
useEffect(() => { (async () => { useEffect(() => {
let flings = await flingClient.getFlings(); let flingClient = new FlingClient();
flingClient.getFlings()
.then(flings => {
let newFlings = []; let newFlings = [];
for (let fling of flings) { for (let fling of flings) {
let flingTile = <FlingTile fling={fling} let flingTile = <FlingTile fling={fling} key={fling.id} />;
key={fling.id}
refreshFlingListFn={refreshFlingList} />;
newFlings.push(flingTile); newFlings.push(flingTile);
} }
setFlings(newFlings); setFlings(newFlings);
})(); } , []); }).catch(log.error);
}, []);
return( return(
<div className="panel"> <div className="panel">
@ -31,7 +31,4 @@ export default function FlingList(props) {
</div> </div>
</div> </div>
); );
async function refreshFlingList() {
}
} }

View file

@ -51,8 +51,8 @@ export default function Login() {
authClient.authenticateOwner(opt) authClient.authenticateOwner(opt)
.then(response => { .then(response => {
log.info("Login successful"); log.info("Login successful");
sessionStorage.setItem['token'] = response; sessionStorage.setItem('token', response);
log.info("Returning back to", from); log.debug("Returning back to", from);
history.replace(from); history.replace(from);
}).catch(log.error); }).catch(log.error);
}; };

View file

@ -4,7 +4,7 @@ let clientConfig = (token) => {
let config = new fc.ApiClient(); let config = new fc.ApiClient();
config.basePath = process.env.REACT_APP_API.replace(/\/+$/, ''); 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; } if(token) { config.authentications['bearer'].accessToken = token; }
return config; return config;

26
web/fling/src/util/jwt.js Normal file
View file

@ -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;