import React from 'react';
import { Redirect, generatePath } from 'react-router-dom';
import { Spinner } from 'react-bootstrap';
import { isExpired } from 'react-jwt';
const UserContext = React.createContext();
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
let url = await this.get_oauth_url();
this.setState({url: url});
window.location = url;
}
render() {
if (this.state.url) {
return
;
}
return
Logging in…
;
}
async get_oauth_url() {
let redirect_uri = window.location.origin + generatePath('/oauth-callback');
let response = await fetch('/api/auth/request', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'redirect_uri=' + encodeURIComponent(redirect_uri),
});
let data = await response.json();
if (response.ok && data.url) {
return data.url;
}
}
}
class Logout extends React.Component {
static contextType = UserContext;
constructor(props) {
super(props);
this.state = {done: false};
}
async componentDidMount() {
await this.context.logout();
this.setState({done: true});
}
render() {
if (this.state.done) {
return ;
}
return
Logging out…
;
}
}
class OauthCallback extends React.Component {
static contextType = UserContext;
constructor(props) {
super(props);
this.state = {
error: null,
done: false,
};
}
async componentDidMount() {
this.setState({error: null});
let params = new URLSearchParams(this.props.location.search);
let error = params.get('error');
if (error) {
this.setState({error: params.get('error_description')});
return;
}
let redirect_uri = window.location.origin + generatePath('/oauth-callback');
let response = await fetch('/api/auth/response', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'code=' + encodeURIComponent(params.get('code')) + '&state=' + encodeURIComponent(params.get('state'))
+ '&redirect_uri=' + encodeURIComponent(redirect_uri),
});
let data = await response.json();
if (!response.ok) {
this.setState({error: data.message});
return;
}
const user = await this.context.get_user();
if (user) {
this.setState({error: null, done: true});
window.flash({header: 'Logged in', text: `You are now logged in as ${user.username}.`});
} else {
window.flash({header: 'Login failed', text: 'Something failed during authentication.'});
}
}
render() {
if (this.state.error) {
return this.state.error;
}
if (this.state.done) {
return ;
}
return
Logging in…
;
}
}
class AuthenticationProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
username: null,
is_logged_in: false,
access_token: null,
};
this.logout = this.logout.bind(this);
this.get_user = this.get_user.bind(this);
this.get_token = this.get_token.bind(this);
}
async componentDidMount() {
await this.get_user();
}
render() {
return
{this.props.children}
;
}
async logout() {
const was_logged_in = this.state.is_logged_in;
await fetch('/api/auth/logout', {
method: 'POST',
});
this.setState({username: null, is_logged_in: false, access_token: null});
if (was_logged_in) {
window.flash({header: 'Logged out', text: 'You are now logged out.'});
}
}
async get_user() {
let token = await this.get_token();
if (!token) {
return;
}
let response = await fetch('/api/user',{
headers: {
Authorization: `Bearer ${token}`,
}
});
if (response.ok) {
let data = await response.json();
this.setState({username: data.username, is_logged_in: true});
return data;
} else {
await this.logout();
}
}
async refresh() {
let response = await fetch('/api/auth/refresh', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: '',
});
if (response.ok) {
let data = await response.json();
return data.access_token;
}
}
async get_token() {
let access_token = this.state.access_token;
let is_expired = !access_token || isExpired(access_token);
if (is_expired) {
access_token = await this.refresh();
this.setState({access_token: access_token});
}
return access_token;
}
}
export { UserContext, Login, Logout, OauthCallback, AuthenticationProvider };