Fling user access
Authentication for protected flings: - Unlock screen - Authentication backend - Routes Direct redirect for unprotected flings.
This commit is contained in:
parent
2fd30c6058
commit
0fe28d3db8
13 changed files with 179 additions and 11 deletions
|
@ -51,16 +51,17 @@ public class AuthenticationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String authenticate(UserAuthDto userAuth) {
|
public String authenticate(UserAuthDto userAuth) {
|
||||||
Long flingId = userAuth.getFlingId();
|
var fling = flingService.findFlingByShareId(userAuth.getShareId())
|
||||||
|
.orElseThrow();
|
||||||
String authCode = userAuth.getCode();
|
String authCode = userAuth.getCode();
|
||||||
|
|
||||||
if (!flingService.hasAuthCode(flingId, authCode)) {
|
if (!flingService.hasAuthCode(fling.getId(), authCode)) {
|
||||||
throw new AccessDeniedException("Wrong fling code");
|
throw new AccessDeniedException("Wrong fling code");
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeBaseBuilder()
|
return makeBaseBuilder()
|
||||||
.setSubject("user")
|
.setSubject("user")
|
||||||
.claim("fid", flingId)
|
.claim("sid", fling.getShareUrl())
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class UserAuthDto {
|
public class UserAuthDto {
|
||||||
Long flingId;
|
String shareId;
|
||||||
String code;
|
String code;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ public class FlingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
var flingEntity = flingMapper.map(flingDto);
|
var flingEntity = flingMapper.map(flingDto);
|
||||||
|
flingEntity.setAuthCode(hashKey(flingEntity.getAuthCode()));
|
||||||
flingEntity = flingRepository.save(flingEntity);
|
flingEntity = flingRepository.save(flingEntity);
|
||||||
return flingEntity.getId();
|
return flingEntity.getId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,23 @@ import React from 'react';
|
||||||
|
|
||||||
import {Switch, Route, Redirect} from "react-router-dom";
|
import {Switch, Route, Redirect} from "react-router-dom";
|
||||||
|
|
||||||
import request, {isOwner} from './util/request';
|
import request, {isOwner, isUser} from './util/request';
|
||||||
|
|
||||||
import Login from './components/admin/Login';
|
import Login from './components/admin/Login';
|
||||||
import Fling from './components/admin/Fling';
|
import FlingAdmin from './components/admin/FlingAdmin';
|
||||||
|
|
||||||
|
import Unlock from './components/user/Unlock';
|
||||||
|
import FlingUser from './components/user/FlingUser';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/admin/login" component={Login} />
|
<Route exact path="/admin/login" component={Login} />
|
||||||
<OwnerRoute exact path="/admin"><Fling /></OwnerRoute>
|
<OwnerRoute exact path="/admin"><FlingAdmin /></OwnerRoute>
|
||||||
<OwnerRoute path="/admin/:fling"><Fling /></OwnerRoute>
|
<OwnerRoute path="/admin/:fling"><FlingAdmin /></OwnerRoute>
|
||||||
|
|
||||||
|
<Route exact path="/unlock" component={Unlock} />
|
||||||
|
<UserRoute exact path="/f/:shareId"><FlingUser /></UserRoute>
|
||||||
<Route match="*">Not implemented</Route>
|
<Route match="*">Not implemented</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
@ -33,3 +39,23 @@ function OwnerRoute({ children, ...rest }) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A wrapper for <Route> that redirects to the unlock
|
||||||
|
// screen if the fling is protected
|
||||||
|
function UserRoute({ children, ...rest }) {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={({ match, location }) => {
|
||||||
|
log.info(request.defaults);
|
||||||
|
log.info(match);
|
||||||
|
log.info(location);
|
||||||
|
let x = {from: location, shareId: match.params.shareId};
|
||||||
|
|
||||||
|
if(isOwner()) { return children; }
|
||||||
|
else if(isUser(match.params.shareId)) { return children; }
|
||||||
|
else { return <Redirect to={ {pathname: "/unlock", state: x} } />; }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import FlingContent from './FlingContent';
|
||||||
|
|
||||||
import {useParams, BrowserRouter} from 'react-router-dom';
|
import {useParams, BrowserRouter} from 'react-router-dom';
|
||||||
|
|
||||||
export default function Fling() {
|
export default function FlingAdmin() {
|
||||||
let { fling } = useParams();
|
let { fling } = useParams();
|
||||||
|
|
||||||
return(
|
return(
|
|
@ -64,11 +64,22 @@ function FlingArtifactRow(props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FlingInfo(props) {
|
||||||
|
return(
|
||||||
|
<div className="m-2">
|
||||||
|
{ /* Add some infos about the fling */ }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function FlingArtifacts(props) {
|
export default function FlingArtifacts(props) {
|
||||||
const [artifacts, setArtifacts] = useState([]);
|
const [artifacts, setArtifacts] = useState([]);
|
||||||
useEffect(getArtifacts, [props.activeFling]);
|
useEffect(getArtifacts, [props.activeFling]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
<FlingInfo />
|
||||||
|
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -82,6 +93,7 @@ export default function FlingArtifacts(props) {
|
||||||
{artifacts}
|
{artifacts}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function getArtifacts() {
|
function getArtifacts() {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import request, {setAuth} from '../../util/request';
|
||||||
|
|
||||||
import Error from './Error';
|
import Error from './Error';
|
||||||
|
|
||||||
export default () => {
|
export default function Login() {
|
||||||
const [errors, setErrors] = useState([]);
|
const [errors, setErrors] = useState([]);
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
|
@ -45,6 +45,7 @@ export default function New(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose(ev) {
|
function handleClose(ev) {
|
||||||
|
if(ev) ev.preventDefault(); // this is needed, otherwise a submit event is fired
|
||||||
props.closeModalFn();
|
props.closeModalFn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
web/fling/src/components/user/Error.jsx
Normal file
26
web/fling/src/components/user/Error.jsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import log from 'loglevel';
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
function renderError() {
|
||||||
|
return (
|
||||||
|
<div className="toast toast-error mb-2">
|
||||||
|
<button className="btn btn-clear float-right" onClick={props.clearErrors}></button>
|
||||||
|
<h5>Ooops!</h5>
|
||||||
|
<li>
|
||||||
|
{ props.errors.map( (err, idx) => <ul key={idx}>{err}</ul> ) }
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ props.errors.length > 0 && !props.below ? renderError() : "" }
|
||||||
|
{ props.children }
|
||||||
|
{ props.errors.length > 0 && props.below ? renderError() : "" }
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
14
web/fling/src/components/user/FlingUser.jsx
Normal file
14
web/fling/src/components/user/FlingUser.jsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import log from 'loglevel';
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
|
||||||
|
import {useParams, BrowserRouter} from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function FlingAdmin() {
|
||||||
|
let { fling } = useParams();
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
Hello
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
70
web/fling/src/components/user/Unlock.jsx
Normal file
70
web/fling/src/components/user/Unlock.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import log from 'loglevel';
|
||||||
|
import React, {useState, useEffect} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router-dom';
|
||||||
|
|
||||||
|
import request, {setAuth} from '../../util/request';
|
||||||
|
|
||||||
|
import Error from './Error';
|
||||||
|
|
||||||
|
export default function Unlock() {
|
||||||
|
const [errors, setErrors] = useState([]);
|
||||||
|
const [authCode, setAuthCode] = useState("");
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const { from, shareId } = location.state || { from: { pathname: "/admin" }, shareId: "" };
|
||||||
|
|
||||||
|
useEffect(() => setAuth(null), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
request.post("/auth/user", {"shareId": location.state.shareId})
|
||||||
|
.then(response => {
|
||||||
|
log.info("Fling is not protected. Logged in successfully.");
|
||||||
|
setAuth(response.data);
|
||||||
|
history.replace(location.state.from);
|
||||||
|
})
|
||||||
|
.catch(err => {/* ignored */});
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container-center">
|
||||||
|
<div>
|
||||||
|
<div className="column col-12">
|
||||||
|
<h1>This Fling is <span className="text-primary">locked.</span></h1>
|
||||||
|
</div>
|
||||||
|
<div className="column col-12">
|
||||||
|
<form id="auth-code-form" onSubmit={handleSubmit}>
|
||||||
|
<div className="input-group">
|
||||||
|
<input id="auth-code" className="form-input" name="authCode" type="password" placeholder="Enter your code" value={authCode} onChange={handleChange} />
|
||||||
|
<button className="btn btn-primary" type="submit">Unlock</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleSubmit(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
request.post("/auth/user", {"shareId": shareId, "code": authCode})
|
||||||
|
.then(response => {
|
||||||
|
log.info("Logged in successfully");
|
||||||
|
setAuth(response.data);
|
||||||
|
history.replace(from);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
log.error(error);
|
||||||
|
let response = error.response;
|
||||||
|
response.data && response.data.message && setErrors( prev => [response.data.message, ...prev] );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleChange(ev) {
|
||||||
|
let val = ev.target.value;
|
||||||
|
setAuthCode(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
function clearErrors() {
|
||||||
|
setErrors([]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,11 @@ $light-grey: #ebeced;
|
||||||
$dark-grey: #464748;
|
$dark-grey: #464748;
|
||||||
$red: #e53e3e;
|
$red: #e53e3e;
|
||||||
$black: #323334;
|
$black: #323334;
|
||||||
|
$lighter-grey: #E9F2F9;
|
||||||
|
|
||||||
// Semantic colors
|
// Semantic colors
|
||||||
$primary-color: #4693d2;
|
$primary-color: #4693d2;
|
||||||
$canvas-base-color: $grey;
|
$canvas-base-color: white;
|
||||||
$navbar-base-color: $black;
|
$navbar-base-color: $black;
|
||||||
$navbar-color: $light-grey;
|
$navbar-color: $light-grey;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,22 @@ body {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**********\
|
||||||
|
| Unlock |
|
||||||
|
\**********/
|
||||||
|
|
||||||
|
#auth-code {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#auth-code-form .btn {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
/*********\
|
/*********\
|
||||||
| Navbar |
|
| Navbar |
|
||||||
\*********/
|
\*********/
|
||||||
|
|
Loading…
Reference in a new issue