Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#github-username #743

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
12 changes: 7 additions & 5 deletions frontend/src/static/js/components/webstatus-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import {consume} from '@lit/context';
import {LitElement, type TemplateResult, html, nothing} from 'lit';
import {customElement, state} from 'lit/decorators.js';

import {User} from 'firebase/auth';
import {firebaseUserContext} from '../contexts/firebase-user-context.js';
import {
FirebaseUser,
firebaseUserContext,
} from '../contexts/firebase-user-context.js';
import {
AuthConfig,
firebaseAuthContext,
Expand All @@ -34,7 +36,7 @@ export class WebstatusLogin extends LitElement {

@consume({context: firebaseUserContext, subscribe: true})
@state()
user?: User;
user?: FirebaseUser;

handleLogInClick(authConfig: AuthConfig) {
if (this.user === undefined) {
Expand Down Expand Up @@ -72,14 +74,14 @@ export class WebstatusLogin extends LitElement {
}

renderAuthenticatedButton(
user: User,
user: FirebaseUser,
authConfig: AuthConfig
): TemplateResult {
return html`
<sl-dropdown>
<sl-button slot="trigger" caret
><sl-icon slot="prefix" name="${authConfig.icon}"></sl-icon
>${user.email}</sl-button
>${user?.gitHubUsername || user.email}</sl-button
>
<sl-menu>
<sl-menu-item @click=${() => this.handleLogOutClick(authConfig)}
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/static/js/contexts/firebase-user-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import {createContext} from '@lit/context';
import type {User} from 'firebase/auth';
export type {User} from 'firebase/auth';

export const firebaseUserContext = createContext<User | undefined>(
export const firebaseUserContext = createContext<FirebaseUser | undefined>(
'firebase-user'
);

type FirebaseUser = User & {
gitHubUsername: string | null;
};

export type {FirebaseUser};
80 changes: 75 additions & 5 deletions frontend/src/static/js/services/webstatus-firebase-auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import {customElement, property, state} from 'lit/decorators.js';
import {consume, provide} from '@lit/context';
import {Task} from '@lit/task';
import {
FirebaseApp,
firebaseAppContext,
Expand All @@ -31,14 +32,19 @@ import {
getAuth,
signInWithPopup,
} from 'firebase/auth';
import {User, firebaseUserContext} from '../contexts/firebase-user-context.js';
import {
User,
FirebaseUser,
firebaseUserContext,
} from '../contexts/firebase-user-context.js';
import {ServiceElement} from './service-element.js';

interface FirebaseAuthSettings {
emulatorURL: string;
tenantID: string;
}

const GITHUB_USERNAME_REQUEST_URL: string = 'https://api.github.com/user/';

@customElement('webstatus-firebase-auth-service')
export class WebstatusFirebaseAuthService extends ServiceElement {
@property({type: Object})
Expand All @@ -52,14 +58,41 @@ export class WebstatusFirebaseAuthService extends ServiceElement {
firebaseAuthConfig?: AuthConfig;

@provide({context: firebaseUserContext})
user?: User;
user?: FirebaseUser;

_loadingGithubUsername?: Task;

// Useful for testing
authInitializer: (app: FirebaseApp | undefined) => Auth = getAuth;

// Useful for testing
emulatorConnector: (auth: Auth, url: string) => void = connectAuthEmulator;

handleGithubUsernameError(res: Response, email: string) {
const err = new Error();
if (res.status === 403 && res.headers.get('X-RateLimit-Remaining') == '0') {
const resetsAtMS = Number(`${res.headers.get('X-RateLimit-Reset')}000`);
err.message = `Status: ${res.status}, Rate limit exceeded, try again in ${Math.ceil((resetsAtMS - Date.now()) / 60000)}m`;
} else if (res.status === 404) {
err.message = `Status: ${res.status}, Could not find user data for github: ${email}`;
} else {
// add other cases if you want to handle them
err.message = `Unexpected status code: ${res.status}`;
}
throw err;
}

async getGithubUser(token: string): Promise<any> {
return fetch(`${GITHUB_USERNAME_REQUEST_URL}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
});
}

initFirebaseAuth() {
if (this.firebaseApp) {
const auth = this.authInitializer(this.firebaseApp);
Expand All @@ -84,8 +117,45 @@ export class WebstatusFirebaseAuthService extends ServiceElement {
// Set up the callback that will detect when:
// 1. The user first logs in
// 2. Resuming a session
this.firebaseAuthConfig.auth.onAuthStateChanged(user => {
this.user = user ? user : undefined;
this.firebaseAuthConfig.auth.onAuthStateChanged((user: User | null) => {
if (user !== null) {
this.user =
user !== null ? {...user, gitHubUsername: 'empty'} : undefined;
this._loadingGithubUsername = new Task(this, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will continue to make a request for the information. However, we should:

  1. use octokit. This should simplify the retry logic since the client handles it automatically.
  2. Try to do it without the Task. We actually shouldn't need it. That should make this a lot simpler too.

For step 1: run cd frontend && npm install octokit

args: (): [any] => [this.user],
task: async ([firebaseUser]: [any]) => {
try {
if (
firebaseUser &&
typeof firebaseUser !== 'string' &&
firebaseUser.accessToken
) {
const res: Response = await this.getGithubUser(
firebaseUser.accessToken
);
if (res.ok) {
const data = await res.json();
if (this.user !== undefined) {
this.user = {
...this.user,
gitHubUsername: data.login,
};
}
} else {
this.handleGithubUsernameError(res, this.user?.email || '');
}
} else {
throw new Error('Github username request failed.');
}
} catch (e: any) {
console.error(e.message);
}
},
});
this._loadingGithubUsername.run();
} else {
throw new Error('Github username request failed.');
}
});
}
}
Expand Down