summaryrefslogtreecommitdiff
path: root/frontend/src/auth.js
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/auth.js')
-rw-r--r--frontend/src/auth.js185
1 files changed, 185 insertions, 0 deletions
diff --git a/frontend/src/auth.js b/frontend/src/auth.js
new file mode 100644
index 0000000..0ed1e1a
--- /dev/null
+++ b/frontend/src/auth.js
@@ -0,0 +1,185 @@
+import React from 'react';
+import { Redirect, generatePath } from 'react-router-dom';
+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 <div>Redirecting to <a href={this.state.url}>{this.state.url}</a></div>;
+ }
+ return <div>Logging in…</div>;
+ }
+ 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 <Redirect to='/' />;
+ }
+ return <div>Logging out…</div>;
+ }
+}
+
+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 <Redirect to='/' />;
+ }
+ 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 <UserContext.Provider value={{
+ username: this.state.username,
+ is_logged_in: this.state.is_logged_in,
+ get_user: this.get_user,
+ logout: this.logout,
+ get_token: this.get_token}}>
+ {this.props.children}
+ </UserContext.Provider>;
+ }
+ 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 };