Add Firebase-powered GitHub login (#215)
This adds GitHub login via Firebase, as a foundation for adding a gallery (#26). Currently it just adds a login/logout button to the Debug page. At an architectural level, this adds an `AuthContext` React context, whose interface is generic enough that it's not coupled to Firebase or GitHub. In fact, the default context is a "null context" whose operations are all no-ops.pull/216/head
rodzic
ed23d0ac3b
commit
f927db60c0
|
@ -0,0 +1,103 @@
|
|||
import { FirebaseApp, FirebaseOptions, initializeApp } from "firebase/app";
|
||||
import {
|
||||
getAuth,
|
||||
signInWithPopup,
|
||||
GithubAuthProvider,
|
||||
onAuthStateChanged,
|
||||
signOut,
|
||||
Auth,
|
||||
User,
|
||||
} from "firebase/auth";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
const DEFAULT_APP_CONFIG: FirebaseOptions = {
|
||||
apiKey: "AIzaSyAV1kkVvSKEicEa8rLke9o_BxYBu1rb8kw",
|
||||
authDomain: "mystic-addaf.firebaseapp.com",
|
||||
projectId: "mystic-addaf",
|
||||
storageBucket: "mystic-addaf.appspot.com",
|
||||
messagingSenderId: "26787182745",
|
||||
appId: "1:26787182745:web:e4fbd9439b9279fe966008",
|
||||
measurementId: "G-JHKRSK1PR6",
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic interface for authentication.
|
||||
*/
|
||||
export interface AuthContext {
|
||||
/**
|
||||
* The currently logged-in user. This will be
|
||||
* null if the user isn't logged in, otherwise it will
|
||||
* be their name.
|
||||
*/
|
||||
loggedInUser: string | null;
|
||||
|
||||
/**
|
||||
* The name of the authentication provider, e.g. "GitHub",
|
||||
* or null if auth is disabled.
|
||||
*/
|
||||
providerName: string | null;
|
||||
|
||||
/**
|
||||
* If authentication failed for some reason, this will
|
||||
* be a string describing the error.
|
||||
*/
|
||||
error?: string;
|
||||
|
||||
/** Begin the login UI flow. */
|
||||
login(): void;
|
||||
|
||||
/** Log out the user. */
|
||||
logout(): void;
|
||||
}
|
||||
|
||||
type FirebaseGithubAuthState = {
|
||||
app: FirebaseApp;
|
||||
auth: Auth;
|
||||
provider: GithubAuthProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Firebase GitHub authentication provider.
|
||||
*
|
||||
* Note this component is assumed to never be unmounted, nor
|
||||
* for its props to change.
|
||||
*/
|
||||
export const FirebaseGithubAuthProvider: React.FC<{
|
||||
config?: FirebaseOptions;
|
||||
}> = ({ config, children }) => {
|
||||
const [state, setState] = useState<FirebaseGithubAuthState | null>(null);
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
const handleError = (e: Error) => setError(e.message);
|
||||
|
||||
useEffect(() => {
|
||||
const app = initializeApp(config || DEFAULT_APP_CONFIG);
|
||||
const auth = getAuth(app);
|
||||
const provider = new GithubAuthProvider();
|
||||
|
||||
setState({ app, auth, provider });
|
||||
onAuthStateChanged(auth, setUser);
|
||||
}, [config]);
|
||||
|
||||
const context: AuthContext = {
|
||||
loggedInUser: user && user.displayName,
|
||||
providerName: "GitHub",
|
||||
error,
|
||||
login: useCallback(() => {
|
||||
state && signInWithPopup(state.auth, state.provider).catch(handleError);
|
||||
}, [state]),
|
||||
logout: useCallback(() => {
|
||||
state && signOut(state.auth).catch(handleError);
|
||||
}, [state]),
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={context} children={children} />;
|
||||
};
|
||||
|
||||
export const AuthContext = React.createContext<AuthContext>({
|
||||
loggedInUser: null,
|
||||
providerName: null,
|
||||
login() {},
|
||||
logout() {},
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { FirebaseGithubAuthProvider } from "./auth";
|
||||
import { PageContext, PAGE_QUERY_ARG } from "./page";
|
||||
import { pageNames, Pages, toPageName, DEFAULT_PAGE } from "./pages";
|
||||
|
||||
|
@ -56,9 +57,11 @@ const App: React.FC<{}> = (props) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<PageContext.Provider value={ctx}>
|
||||
<PageComponent />
|
||||
</PageContext.Provider>
|
||||
<FirebaseGithubAuthProvider>
|
||||
<PageContext.Provider value={ctx}>
|
||||
<PageComponent />
|
||||
</PageContext.Provider>
|
||||
</FirebaseGithubAuthProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import { AuthContext } from "../auth";
|
||||
import { AutoSizingSvg } from "../auto-sizing-svg";
|
||||
import { CreatureContext, CreatureContextType } from "../creature-symbol";
|
||||
import { createCreatureSymbolFactory } from "../creature-symbol-factory";
|
||||
|
@ -93,6 +94,24 @@ const RandomColorSampling: React.FC<{}> = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const AuthWidget: React.FC<{}> = () => {
|
||||
const ctx = useContext(AuthContext);
|
||||
|
||||
if (!ctx.providerName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ctx.error) {
|
||||
return <p style={{ color: "red" }}>{ctx.error}</p>;
|
||||
}
|
||||
|
||||
if (ctx.loggedInUser) {
|
||||
return <button onClick={ctx.logout}>Logout {ctx.loggedInUser}</button>;
|
||||
}
|
||||
|
||||
return <button onClick={ctx.login}>Login with {ctx.providerName}</button>;
|
||||
};
|
||||
|
||||
export const DebugPage: React.FC<{}> = () => {
|
||||
const [symbolCtx, setSymbolCtx] = useState(createSvgSymbolContext());
|
||||
const defaultCtx = useContext(CreatureContext);
|
||||
|
@ -108,6 +127,7 @@ export const DebugPage: React.FC<{}> = () => {
|
|||
<SymbolContextWidget ctx={symbolCtx} onChange={setSymbolCtx} />
|
||||
<h2>Random color sampling</h2>
|
||||
<RandomColorSampling />
|
||||
<AuthWidget />
|
||||
</div>
|
||||
<div className="canvas">
|
||||
<CreatureContext.Provider value={ctx}>
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -49,6 +49,7 @@
|
|||
"classnames": "^2.3.1",
|
||||
"colorspaces": "^0.1.5",
|
||||
"esbuild": "^0.12.5",
|
||||
"firebase": "^9.0.0-beta.7",
|
||||
"gh-pages": "^3.1.0",
|
||||
"hsluv": "^0.1.0",
|
||||
"jest": "^26.6.3",
|
||||
|
|
Ładowanie…
Reference in New Issue