0.1 #1
6 changed files with 87 additions and 95 deletions
|
@ -15,12 +15,14 @@ import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.JwtBuilder;
|
import io.jsonwebtoken.JwtBuilder;
|
||||||
import io.jsonwebtoken.JwtParser;
|
import io.jsonwebtoken.JwtParser;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||||
|
import net.friedl.fling.model.dto.FlingDto;
|
||||||
import net.friedl.fling.model.dto.UserAuthDto;
|
import net.friedl.fling.model.dto.UserAuthDto;
|
||||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||||
import net.friedl.fling.persistence.entities.TokenEntity;
|
import net.friedl.fling.persistence.entities.TokenEntity;
|
||||||
|
@ -84,10 +86,14 @@ public class AuthenticationService {
|
||||||
throw new EntityNotFoundException("No entity for shareId=" + userAuth.getShareId());
|
throw new EntityNotFoundException("No entity for shareId=" + userAuth.getShareId());
|
||||||
}
|
}
|
||||||
|
|
||||||
String providedAuthCodeHash = passwordEncoder.encode(userAuth.getAuthCode());
|
String providedAuthCode = userAuth.getAuthCode();
|
||||||
String actualAuthCodeHash = flingEntity.getAuthCode();
|
String actualAuthCodeHash = flingEntity.getAuthCode();
|
||||||
|
|
||||||
if (!actualAuthCodeHash.equals(providedAuthCodeHash)) {
|
Boolean isProtected = StringUtils.hasText(actualAuthCodeHash);
|
||||||
|
|
||||||
|
if(!isProtected) log.debug("No protection set for fling [.shareId={}]");
|
||||||
|
|
||||||
|
if (isProtected && !passwordEncoder.matches(providedAuthCode, actualAuthCodeHash)) {
|
||||||
log.debug("Authentication failed for fling [.shareId={}]", userAuth.getShareId());
|
log.debug("Authentication failed for fling [.shareId={}]", userAuth.getShareId());
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
@ -96,6 +102,7 @@ public class AuthenticationService {
|
||||||
return Optional.of(
|
return Optional.of(
|
||||||
getJwtBuilder()
|
getJwtBuilder()
|
||||||
.setSubject("user")
|
.setSubject("user")
|
||||||
|
.claim("shareId", flingEntity.getShareId())
|
||||||
.claim("id", flingEntity.getId())
|
.claim("id", flingEntity.getId())
|
||||||
.compact());
|
.compact());
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,16 @@ public class AuthenticationServiceTest {
|
||||||
assertThat(authenticationService.authenticate(userAuthDto), equalTo(Optional.empty()));
|
assertThat(authenticationService.authenticate(userAuthDto), equalTo(Optional.empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticate_authCodeEmpty_ok() {
|
||||||
|
FlingEntity flingEntity = new FlingEntity();
|
||||||
|
UserAuthDto userAuthDto = new UserAuthDto("shareId", "");
|
||||||
|
|
||||||
|
when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity);
|
||||||
|
|
||||||
|
assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticate_authCodeEquals_ok() {
|
public void authenticate_authCodeEquals_ok() {
|
||||||
FlingEntity flingEntity = new FlingEntity();
|
FlingEntity flingEntity = new FlingEntity();
|
||||||
|
@ -130,6 +140,7 @@ public class AuthenticationServiceTest {
|
||||||
|
|
||||||
when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity);
|
when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity);
|
||||||
when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash");
|
when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash");
|
||||||
|
when(passwordEncoder.matches("authCode", "authCodeHash")).thenReturn(true);
|
||||||
|
|
||||||
assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty())));
|
assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ function UserRoute({ children, ...rest }) {
|
||||||
|
|
||||||
let authorized =
|
let authorized =
|
||||||
jwt.hasSubject("admin")
|
jwt.hasSubject("admin")
|
||||||
|| ( jwt.hasSubject("user") && jwt.hasClaim("id", state['shareId']) );
|
|| ( jwt.hasSubject("user") && jwt.hasClaim("shareId", state['shareId']) );
|
||||||
|
|
||||||
if (authorized) { return children; }
|
if (authorized) { return children; }
|
||||||
else { return <Redirect to={{ pathname: "/unlock", state: state }} />; }
|
else { return <Redirect to={{ pathname: "/unlock", state: state }} />; }
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
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() : "" }
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, {useState, useEffect} from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import {useParams} from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {flingClient} from '../../util/flingclient';
|
import { FlingClient } from '../../util/fc';
|
||||||
|
|
||||||
import DirectDownload from './DirectDownload';
|
import DirectDownload from './DirectDownload';
|
||||||
import FlingUserList from './FlingUserList';
|
import FlingUserList from './FlingUserList';
|
||||||
|
@ -12,11 +12,12 @@ export default function FlingUser() {
|
||||||
let [fling, setFling] = useState({});
|
let [fling, setFling] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let flingClient = new FlingClient();
|
||||||
flingClient.getFlingByShareId(shareId)
|
flingClient.getFlingByShareId(shareId)
|
||||||
.then(f => setFling(f));
|
.then(f => setFling(f));
|
||||||
}, [shareId]);
|
}, [shareId]);
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fling.sharing && fling.sharing.directDownload
|
{fling.sharing && fling.sharing.directDownload
|
||||||
? <DirectDownload fling={fling} />
|
? <DirectDownload fling={fling} />
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState, useEffect} from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {useHistory, useLocation} from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import request, {setAuth} from '../../util/request';
|
import { AuthClient, fc } from '../../util/fc';
|
||||||
|
|
||||||
export default function Unlock() {
|
export default function Unlock() {
|
||||||
const [authCode, setAuthCode] = useState("");
|
const [authCode, setAuthCode] = useState("");
|
||||||
|
@ -10,16 +10,18 @@ export default function Unlock() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { from, shareId } = location.state || { from: { pathname: "/admin" }, shareId: "" };
|
const { from, shareId } = location.state || { from: { pathname: "/admin" }, shareId: "" };
|
||||||
|
|
||||||
useEffect(() => setAuth(null), []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
request.post("/auth/user", {"shareId": location.state.shareId})
|
let authClient = new AuthClient();
|
||||||
|
let userAuth = new fc.UserAuth(location.state.shareId, "")
|
||||||
|
|
||||||
|
authClient.authenticateUser({ 'userAuth': userAuth })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
log.info("Fling is not protected. Logged in successfully.");
|
log.info("Fling is not protected. Logged in successfully.");
|
||||||
setAuth(response.data);
|
sessionStorage.setItem('token', response);
|
||||||
history.replace(location.state.from);
|
history.replace(location.state.from);
|
||||||
})
|
}).catch(error => {
|
||||||
.catch(err => {/* ignored */});
|
log.info("Fling protected. Could not unlock without code.")
|
||||||
|
});
|
||||||
}, [location, history]);
|
}, [location, history]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,7 +33,8 @@ export default function Unlock() {
|
||||||
<div className="column col-12">
|
<div className="column col-12">
|
||||||
<form id="auth-code-form" onSubmit={handleSubmit}>
|
<form id="auth-code-form" onSubmit={handleSubmit}>
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input id="auth-code" className="form-input" name="authCode" type="password" placeholder="Enter your code" value={authCode} onChange={handleChange} />
|
<input id="auth-code" className="form-input" name="authCode" type="password" placeholder="Enter your code"
|
||||||
|
value={authCode} onChange={ev => setAuthCode(ev.target.value)} />
|
||||||
<button className="btn btn-primary" type="submit">Unlock</button>
|
<button className="btn btn-primary" type="submit">Unlock</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -42,20 +45,16 @@ export default function Unlock() {
|
||||||
|
|
||||||
function handleSubmit(ev) {
|
function handleSubmit(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
let authClient = new AuthClient();
|
||||||
|
let userAuth = new fc.UserAuth(shareId, authCode)
|
||||||
|
|
||||||
request.post("/auth/user", {"shareId": shareId, "code": authCode})
|
authClient.authenticateUser({ 'userAuth': userAuth })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
log.info("Logged in successfully");
|
log.info("Logged in successfully");
|
||||||
setAuth(response.data);
|
sessionStorage.setItem('token', response);
|
||||||
history.replace(from);
|
history.replace(from);
|
||||||
})
|
}).catch(error => {
|
||||||
.catch(error => {
|
|
||||||
log.error(error);
|
log.error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleChange(ev) {
|
|
||||||
let val = ev.target.value;
|
|
||||||
setAuthCode(val);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue