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
Redirecting to {this.state.url}
; } 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 };