diff --git a/frontend/src/html/pages/login.html b/frontend/src/html/pages/login.html index ab7585225efb..8941db609989 100644 --- a/frontend/src/html/pages/login.html +++ b/frontend/src/html/pages/login.html @@ -77,10 +77,10 @@ Google Sign In - + diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 81ba19a318d5..52cc4475d02e 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -1635,6 +1635,21 @@ +
diff --git a/frontend/src/ts/controllers/account-controller.ts b/frontend/src/ts/controllers/account-controller.ts index b9971a7c7169..14b88875bd42 100644 --- a/frontend/src/ts/controllers/account-controller.ts +++ b/frontend/src/ts/controllers/account-controller.ts @@ -20,6 +20,7 @@ import * as Account from "../pages/account"; import * as Alerts from "../elements/alerts"; import { GoogleAuthProvider, + GithubAuthProvider, browserSessionPersistence, browserLocalPersistence, createUserWithEmailAndPassword, @@ -31,6 +32,7 @@ import { getAdditionalUserInfo, User as UserType, Unsubscribe, + AuthProvider, } from "firebase/auth"; import { Auth, getAuthenticatedUser, isAuthenticated } from "../firebase"; import { dispatch as dispatchSignUpEvent } from "../observables/google-sign-up-event"; @@ -45,6 +47,7 @@ import { getHtmlByUserFlags } from "./user-flag-controller"; let signedOutThisSession = false; export const gmailProvider = new GoogleAuthProvider(); +export const githubProvider = new GithubAuthProvider(); async function sendVerificationEmail(): Promise { if (Auth === undefined) { @@ -266,6 +269,8 @@ if (Auth && ConnectionState.get()) { // ChallengeController.setup(challengeName); // }, 1000); } + + Settings.updateAuthSections(); }); } else { $("nav .signInOut").addClass("hidden"); @@ -357,7 +362,7 @@ async function signIn(): Promise { }); } -async function signInWithGoogle(): Promise { +async function signInWithProvider(provider: AuthProvider): Promise { if (Auth === undefined) { Notifications.add("Authentication uninitialized", -1, { duration: 3, @@ -382,7 +387,7 @@ async function signInWithGoogle(): Promise { : browserSessionPersistence; await setPersistence(Auth, persistence); - signInWithPopup(Auth, gmailProvider) + signInWithPopup(Auth, provider) .then(async (signedInUser) => { if (getAdditionalUserInfo(signedInUser)?.isNewUser) { dispatchSignUpEvent(signedInUser, true); @@ -413,7 +418,32 @@ async function signInWithGoogle(): Promise { }); } +async function signInWithGoogle(): Promise { + return signInWithProvider(gmailProvider); +} + +async function signInWithGitHub(): Promise { + return signInWithProvider(githubProvider); +} + async function addGoogleAuth(): Promise { + return addAuthProvider("Google", gmailProvider); +} + +async function addGithubAuth(): Promise { + return addAuthProvider("GitHub", githubProvider); +} + +async function addAuthProvider( + providerName: string, + provider: AuthProvider +): Promise { + if (!ConnectionState.get()) { + Notifications.add("You are offline", 0, { + duration: 2, + }); + return; + } if (Auth === undefined) { Notifications.add("Authentication uninitialized", -1, { duration: 3, @@ -422,16 +452,16 @@ async function addGoogleAuth(): Promise { } Loader.show(); if (!isAuthenticated()) return; - linkWithPopup(getAuthenticatedUser(), gmailProvider) + linkWithPopup(getAuthenticatedUser(), provider) .then(function () { Loader.hide(); - Notifications.add("Google authentication added", 1); + Notifications.add(`${providerName} authentication added`, 1); Settings.updateAuthSections(); }) .catch(function (error) { Loader.hide(); Notifications.add( - "Failed to add Google authentication: " + error.message, + `Failed to add ${providerName} authentication: ` + error.message, -1 ); }); @@ -620,9 +650,9 @@ $(".pageLogin .login button.signInWithGoogle").on("click", () => { void signInWithGoogle(); }); -// $(".pageLogin .login .button.signInWithGitHub").on("click",(e) => { -// signInWithGitHub(); -// }); +$(".pageLogin .login button.signInWithGitHub").on("click", () => { + void signInWithGitHub(); +}); $("header .signInOut").on("click", () => { if (Auth === undefined) { @@ -645,15 +675,13 @@ $(".pageLogin .register form").on("submit", (e) => { }); $(".pageSettings #addGoogleAuth").on("click", async () => { - if (!ConnectionState.get()) { - Notifications.add("You are offline", 0, { - duration: 2, - }); - return; - } void addGoogleAuth(); }); +$(".pageSettings #addGithubAuth").on("click", async () => { + void addGithubAuth(); +}); + $(".pageAccount").on("click", ".sendVerificationEmail", () => { if (!ConnectionState.get()) { Notifications.add("You are offline", 0, { diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 6869dcac1a49..6df02cfe2c16 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -734,16 +734,20 @@ export function updateDiscordSection(): void { export function updateAuthSections(): void { $(".pageSettings .section.passwordAuthSettings button").addClass("hidden"); $(".pageSettings .section.googleAuthSettings button").addClass("hidden"); + $(".pageSettings .section.githubAuthSettings button").addClass("hidden"); if (!isAuthenticated()) return; const user = getAuthenticatedUser(); - const passwordProvider = user.providerData.find( + const passwordProvider = user.providerData.some( (provider) => provider.providerId === "password" ); - const googleProvider = user.providerData.find( + const googleProvider = user.providerData.some( (provider) => provider.providerId === "google.com" ); + const githubProvider = user.providerData.some( + (provider) => provider.providerId === "github.com" + ); if (passwordProvider) { $( @@ -762,7 +766,7 @@ export function updateAuthSections(): void { $( ".pageSettings .section.googleAuthSettings #removeGoogleAuth" ).removeClass("hidden"); - if (passwordProvider) { + if (passwordProvider || githubProvider) { $( ".pageSettings .section.googleAuthSettings #removeGoogleAuth" ).removeClass("disabled"); @@ -776,6 +780,24 @@ export function updateAuthSections(): void { "hidden" ); } + if (githubProvider) { + $( + ".pageSettings .section.githubAuthSettings #removeGithubAuth" + ).removeClass("hidden"); + if (passwordProvider || googleProvider) { + $( + ".pageSettings .section.githubAuthSettings #removeGithubAuth" + ).removeClass("disabled"); + } else { + $(".pageSettings .section.githubAuthSettings #removeGithubAuth").addClass( + "disabled" + ); + } + } else { + $(".pageSettings .section.githubAuthSettings #addGithubAuth").removeClass( + "hidden" + ); + } } function setActiveFunboxButton(): void { diff --git a/frontend/src/ts/popups/simple-popups.ts b/frontend/src/ts/popups/simple-popups.ts index 976a38a983c1..1922f219b0bb 100644 --- a/frontend/src/ts/popups/simple-popups.ts +++ b/frontend/src/ts/popups/simple-popups.ts @@ -59,6 +59,7 @@ type PopupKey = | "updateName" | "updatePassword" | "removeGoogleAuth" + | "removeGithubAuth" | "addPasswordAuth" | "deleteAccount" | "resetAccount" @@ -86,6 +87,7 @@ const list: Record = { updateName: undefined, updatePassword: undefined, removeGoogleAuth: undefined, + removeGithubAuth: undefined, addPasswordAuth: undefined, deleteAccount: undefined, resetAccount: undefined, @@ -424,7 +426,13 @@ async function reauthenticate( ); await reauthenticateWithCredential(user, credential); } else if (method === "passwordFirst") { - await reauthenticateWithPopup(user, AccountController.gmailProvider); + const isGithub = user.providerData.some( + (p) => p?.providerId === "github.com" + ); + const authProvider = isGithub + ? AccountController.githubProvider + : AccountController.gmailProvider; + await reauthenticateWithPopup(user, authProvider); } return { @@ -535,7 +543,7 @@ list.removeGoogleAuth = new SimplePopup({ onlineOnly: true, buttonText: "remove", execFn: async (_thisPopup, password): Promise => { - const reauth = await reauthenticate("passwordOnly", password); + const reauth = await reauthenticate("passwordFirst", password); if (reauth.status !== 1) { return { status: reauth.status, @@ -565,8 +573,62 @@ list.removeGoogleAuth = new SimplePopup({ if (!isAuthenticated()) return; if (!isUsingPasswordAuthentication()) { thisPopup.inputs = []; - thisPopup.buttonText = ""; - thisPopup.text = "Password authentication is not enabled"; + if (!isUsingGithubAuthentication()) { + thisPopup.buttonText = ""; + thisPopup.text = "Password or GitHub authentication is not enabled"; + } + } + }, +}); + +list.removeGithubAuth = new SimplePopup({ + id: "removeGithubAuth", + type: "text", + title: "Remove GitHub authentication", + inputs: [ + { + placeholder: "Password", + type: "password", + initVal: "", + }, + ], + onlineOnly: true, + buttonText: "remove", + execFn: async (_thisPopup, password): Promise => { + const reauth = await reauthenticate("passwordFirst", password); + if (reauth.status !== 1) { + return { + status: reauth.status, + message: reauth.message, + }; + } + + try { + await unlink(reauth.user, "github.com"); + } catch (e) { + const message = createErrorMessage(e, "Failed to unlink GitHub account"); + return { + status: -1, + message, + }; + } + + Settings.updateAuthSections(); + + reloadAfter(3); + return { + status: 1, + message: "GitHub authentication removed", + }; + }, + beforeInitFn: (thisPopup): void => { + if (!isAuthenticated()) return; + if (!isUsingPasswordAuthentication()) { + thisPopup.inputs = []; + if (!isUsingGoogleAuthentication()) { + thisPopup.buttonText = ""; + thisPopup.text = "Password or Google authentication is not enabled"; + } } }, }); @@ -1542,13 +1604,24 @@ list.forgotPassword = new SimplePopup({ }); function isUsingPasswordAuthentication(): boolean { + return isUsingAuthentication("password"); +} + +function isUsingGithubAuthentication(): boolean { + return isUsingAuthentication("github.com"); +} + +function isUsingGoogleAuthentication(): boolean { + return isUsingAuthentication("google.com"); +} + +function isUsingAuthentication(authProvider: string): boolean { return ( - Auth?.currentUser?.providerData.find( - (p) => p?.providerId === "password" - ) !== undefined + Auth?.currentUser?.providerData.some( + (p) => p.providerId === authProvider + ) || false ); } - export function showPopup( key: PopupKey, showParams = [] as string[], @@ -1582,6 +1655,10 @@ $(".pageSettings #removeGoogleAuth").on("click", () => { showPopup("removeGoogleAuth"); }); +$(".pageSettings #removeGithubAuth").on("click", () => { + showPopup("removeGithubAuth"); +}); + $("#resetSettingsButton").on("click", () => { showPopup("resetSettings"); });