Skip to content

Commit

Permalink
FIX: Auth session should be persisted after refresh (closes #163)
Browse files Browse the repository at this point in the history
  • Loading branch information
louisduhalberruer committed Jun 19, 2024
1 parent fa55f08 commit de83274
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
70 changes: 70 additions & 0 deletions frontend/src/components/Menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import '../styles/Menu.css';

import Navbar from 'react-bootstrap/Navbar';
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import Form from 'react-bootstrap/Form';
import { Link } from 'react-router-dom';
import { useState } from 'react';

function Menu({backend}) {
return (
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand>
<Link to="/">
<img
src="/logo.png"
height="30"
alt="Index"
/>
</Link>
</Navbar.Brand>
<Authentication {...{backend}} />
</Container>
</Navbar>
);
}

function Authentication({backend}) {
const [credentials, setCredentials] = useState({
...backend.credentials
});

let handleSubmit = (e) => {
e.preventDefault();
let credentials = Object.fromEntries(new FormData(e.target).entries());
backend.authenticate(credentials)
.then(() => setCredentials(credentials));
};

if (credentials.name) return (
<Navbar.Text>
{credentials.name}
</Navbar.Text>
);
return (
<Form onSubmit={handleSubmit}>
<Row className="g-1">
<Col>
<input placeholder="Username" name="name"
className="form-control-sm"
/>
</Col>
<Col>
<input placeholder="Password" name="password" type="password"
className="form-control-sm"
/>
</Col>
<Col>
<button className="btn btn-outline-light" type="submit">
Sign in
</button>
</Col>
</Row>
</Form>
);
}

export default Menu;
127 changes: 127 additions & 0 deletions frontend/src/hyperglosae.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {Buffer} from 'buffer';

const service = 'http://localhost:5984/hyperglosae';

const LOCALSTORAGE_BASIC_AUTH_KEY = 'hyperglosae-basic-auth';

function Hyperglosae(logger) {
this.credentials = {};

let persistBasicAuth = () => {
if (!this.credentials) return;
localStorage.setItem(LOCALSTORAGE_BASIC_AUTH_KEY, JSON.stringify(this.credentials));
};

let retrieveBasicAuth = () => {
if (localStorage.getItem(LOCALSTORAGE_BASIC_AUTH_KEY)) {
try {
const basicAuth = JSON.parse(localStorage.getItem(LOCALSTORAGE_BASIC_AUTH_KEY));
this.credentials = {...this.credentials, ...basicAuth};
} catch {
console.error('Unable to parse basic auth');
localStorage.removeItem(LOCALSTORAGE_BASIC_AUTH_KEY);
}
}
};

retrieveBasicAuth();

this.getView = ({view, id, options = []}) =>
fetch(`${
service
}/_design/app/_view/${
view
}?${
id ? `startkey=["${id}"]&endkey=["${id}",{}]` : ''
}&${
options.map(x => x + '=true').join('&')
}`)
.then(x => x.json())
.then(x => x.rows);

this.getDocument = (id) =>
fetch(`${service}/${id}`)
.then(x => x.json());

let basicAuthentication = ({force}) => {
retrieveBasicAuth();
let { base64} = this.credentials;
if (!force && !base64) return ({});
return ({
'Authorization': 'Basic ' + base64
});
};

this.putDocument = (doc) =>
fetch(`${service}/${doc._id}`, {
method: 'PUT',
headers: basicAuthentication({force: false}),
body: JSON.stringify(doc)
})
.then(x => x.json())
.then(x => {
if (x.reason) {
logger(x.reason);
throw new Error(x.reason);
}
});

this.authenticate = ({name, password}) => {
this.credentials = {
base64: Buffer.from(`${name}:${password}`).toString('base64'),
name
};
persistBasicAuth();

return fetch(`${service}`, {
method: 'GET',
headers: basicAuthentication({force: true})
})
.then(x => x.json())
.then(x => {
if (x.reason) {
this.credentials = {};
localStorage.removeItem(LOCALSTORAGE_BASIC_AUTH_KEY);
logger(x.reason);
throw new Error(x.reason);
}
});
};

this.refreshMetadata = (id, callback) => {
this.getView({view: 'metadata', id, options: ['include_docs']})
.then(
(rows) => {
let documents = rows.map(x => x.doc);
callback(documents);
}
);
};

this.refreshContent = (id, callback) => {
this.getView({view: 'content', id, options: ['include_docs']})
.then(
(rows) => {
callback(rows);
},
(error) => {
console.log(error.message);
}
);
};

this.refreshDocuments = (callback) => {
this.getView({view: 'all_documents', options: ['group']})
.then((rows) => {
callback(
rows.map(
({value}) => ({...value.metadata, referenced: value.referenced})
)
);
});
};

return this;
}

export default Hyperglosae;

0 comments on commit de83274

Please sign in to comment.