Change user unlock to new API
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Armin Friedl 2020-07-25 22:37:31 +02:00
parent 5598cb5ecf
commit 0824d8367f
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
6 changed files with 87 additions and 95 deletions

View file

@ -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();
Boolean isProtected = StringUtils.hasText(actualAuthCodeHash);
if (!actualAuthCodeHash.equals(providedAuthCodeHash)) { 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());

View file

@ -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())));
} }

View file

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

View file

@ -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() : "" }
</>
);
}

View file

@ -1,26 +1,27 @@
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';
export default function FlingUser() { export default function FlingUser() {
let { shareId } = useParams(); let { shareId } = useParams();
let [fling, setFling] = useState({}); let [fling, setFling] = useState({});
useEffect(() => { useEffect(() => {
flingClient.getFlingByShareId(shareId) let flingClient = new FlingClient();
.then(f => setFling(f)); flingClient.getFlingByShareId(shareId)
}, [shareId]); .then(f => setFling(f));
}, [shareId]);
return( return (
<div> <div>
{fling.sharing && fling.sharing.directDownload {fling.sharing && fling.sharing.directDownload
? <DirectDownload fling={fling} /> ? <DirectDownload fling={fling} />
: <FlingUserList fling={fling} />} : <FlingUserList fling={fling} />}
</div> </div>
); );
} }

View file

@ -1,61 +1,60 @@
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("");
const history = useHistory(); const history = useHistory();
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(() => {
let authClient = new AuthClient();
let userAuth = new fc.UserAuth(location.state.shareId, "")
useEffect(() => { authClient.authenticateUser({ 'userAuth': userAuth })
request.post("/auth/user", {"shareId": location.state.shareId}) .then(response => {
.then(response => { log.info("Fling is not protected. Logged in successfully.");
log.info("Fling is not protected. Logged in successfully."); sessionStorage.setItem('token', response);
setAuth(response.data); history.replace(location.state.from);
history.replace(location.state.from); }).catch(error => {
}) log.info("Fling protected. Could not unlock without code.")
.catch(err => {/* ignored */}); });
}, [location, history]); }, [location, history]);
return ( return (
<div className="container-center"> <div className="container-center">
<div> <div>
<div className="column col-12"> <div className="column col-12">
<h1>This Fling is <span className="text-primary">locked.</span></h1> <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> </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={ev => setAuthCode(ev.target.value)} />
<button className="btn btn-primary" type="submit">Unlock</button>
</div>
</form>
</div>
</div>
</div>
);
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);
};
} }