0.1 #1
7 changed files with 105 additions and 67 deletions
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -13,53 +13,64 @@ import FlingUser from './components/user/FlingUser';
|
||||||
import LandingPage from './components/LandingPage';
|
import LandingPage from './components/LandingPage';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={LandingPage} />
|
<Route exact path="/" component={LandingPage} />
|
||||||
|
|
||||||
<Route exact path="/admin/login" component={Login} />
|
<Route exact path="/admin/login" component={Login} />
|
||||||
<OwnerRoute exact path="/admin"><FlingAdmin /></OwnerRoute>
|
<OwnerRoute exact path="/admin"><FlingAdmin /></OwnerRoute>
|
||||||
<OwnerRoute path="/admin/:fling"><FlingAdmin /></OwnerRoute>
|
<OwnerRoute path="/admin/:fling"><FlingAdmin /></OwnerRoute>
|
||||||
|
|
||||||
<Route exact path="/unlock" component={Unlock} />
|
<Route exact path="/unlock" component={Unlock} />
|
||||||
<UserRoute path="/f/:shareId"><FlingUser /></UserRoute>
|
<UserRoute path="/f/:shareId"><FlingUser /></UserRoute>
|
||||||
|
|
||||||
<Route match="*">Not implemented</Route>
|
<Route match="*">Not implemented</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 }) {
|
||||||
return (
|
log.info(`Routing request for ${rest['path']}`);
|
||||||
<Route
|
return (
|
||||||
{...rest}
|
<Route
|
||||||
render={({ location }) => {
|
{...rest}
|
||||||
log.info(request.defaults);
|
render={({ location }) => {
|
||||||
if(isOwner()) { return children; }
|
if (jwt.hasSubject("admin")) { 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 }) {
|
||||||
return (
|
log.debug(`Routing request for ${rest['path']}`);
|
||||||
<Route
|
return (
|
||||||
{...rest}
|
<Route
|
||||||
render={({ match, location }) => {
|
{...rest}
|
||||||
log.info(request.defaults);
|
render={({ match, location }) => {
|
||||||
log.info(match);
|
let state = {from: location, shareId: match.params.shareId};
|
||||||
log.info(location);
|
let authorized = jwt.hasSubject("admin") || (jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId']));
|
||||||
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} } />; }
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,34 @@
|
||||||
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}
|
newFlings.push(flingTile);
|
||||||
refreshFlingListFn={refreshFlingList} />;
|
|
||||||
newFlings.push(flingTile);
|
|
||||||
}
|
}
|
||||||
setFlings(newFlings);
|
setFlings(newFlings);
|
||||||
})(); } , []);
|
}).catch(log.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
{log.info(`Got active fling: ${props.activeFling}`)}
|
{log.info(`Got active fling: ${props.activeFling}`)}
|
||||||
<div className="panel-header p-2">
|
<div className="panel-header p-2">
|
||||||
<h5>My Flings</h5>
|
<h5>My Flings</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-body p-0">
|
<div className="panel-body p-0">
|
||||||
{flings}
|
{flings}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function refreshFlingList() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
26
web/fling/src/util/jwt.js
Normal 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;
|
Loading…
Reference in a new issue