2 Commits 3e66c04c43 ... 09772c28a2

Author SHA1 Message Date
  Owen Diffey 09772c28a2 chore: Remove github auth from docs 2 months ago
  Owen Diffey 70f710f947 refactor: Remove github authentication and set/remove password 2 months ago

+ 1 - 5
.wiki/Configuration.md

@@ -92,15 +92,11 @@ For more information on configuration files please refer to the
 | `apis.recaptcha.enabled` | Whether to enable ReCaptcha in the regular (email) registration form. |
 | `apis.recaptcha.key` | ReCaptcha Site v3 key, obtained from [here](https://www.google.com/recaptcha/admin). |
 | `apis.recaptcha.secret` | ReCaptcha Site v3 secret, obtained with key. |
-| `apis.github.enabled` | Whether to enable GitHub authentication. |
-| `apis.github.client` | GitHub OAuth Application client, obtained from [here](https://github.com/settings/developers). |
-| `apis.github.secret` | GitHub OAuth Application secret, obtained with client. |
-| `apis.github.redirect_uri` | The backend url with `/auth/github/authorize/callback` appended, for example `http://localhost/backend/auth/github/authorize/callback`. This is configured based on the `url` config option by default. |
 | `apis.oidc.enabled` | Whether to enable OIDC authentication. |
 | `apis.oidc.client_id` | OIDC client id. |
 | `apis.oidc.client_secret` | OIDC client secret. |
 | `apis.oidc.openid_configuration_url` | The URL that points to the openid_configuration resource of the OIDC provider. |
-| `apis.oidc.redirect_uri` | The backend url with `/auth/oidc/authorize/callback` appended, for example `http://localhost/backend/auth/github/authorize/callback`. This is configured based on the `url` config option by default, so this is optional. |
+| `apis.oidc.redirect_uri` | The backend url with `/auth/oidc/authorize/callback` appended, for example `http://localhost/backend/auth/oidc/authorize/callback`. This is configured based on the `url` config option by default, so this is optional. |
 | `apis.discogs.enabled` | Whether to enable Discogs API usage. |
 | `apis.discogs.client` | Discogs Application client, obtained from [here](https://www.discogs.com/settings/developers). |
 | `apis.discogs.secret` | Discogs Application secret, obtained with client. |

+ 1 - 1
README.md

@@ -71,7 +71,7 @@ A production demonstration instance of Musare can be found at [demo.musare.com](
   - Activity logs
   - Profile page showing public playlists and activity logs
   - Text or gravatar profile pictures
-  - Email or Github login/registration
+  - Email or OIDC login/registration
   - Preferences to tailor site usage
   - Password reset
   - Data deletion management

+ 0 - 6
backend/config/default.json

@@ -52,12 +52,6 @@
 			"key": "",
 			"secret": ""
 		},
-		"github": {
-			"enabled": false,
-			"client": "",
-			"secret": "",
-			"redirect_uri": ""
-		},
 		"discogs": {
 			"enabled": false,
 			"client": "",

+ 3 - 428
backend/logic/actions/users.js

@@ -3,7 +3,6 @@ import config from "config";
 import async from "async";
 import mongoose from "mongoose";
 
-import axios from "axios";
 import bcrypt from "bcrypt";
 import sha256 from "sha256";
 import isLoginRequired from "../hooks/loginRequired";
@@ -92,39 +91,6 @@ CacheModule.runJob("SUB", {
 	}
 });
 
-CacheModule.runJob("SUB", {
-	channel: "user.unlinkPassword",
-	cb: userId => {
-		WSModule.runJob("SOCKETS_FROM_USER", { userId }).then(sockets => {
-			sockets.forEach(socket => {
-				socket.dispatch("event:user.password.unlinked");
-			});
-		});
-	}
-});
-
-CacheModule.runJob("SUB", {
-	channel: "user.linkGithub",
-	cb: userId => {
-		WSModule.runJob("SOCKETS_FROM_USER", { userId }).then(sockets => {
-			sockets.forEach(socket => {
-				socket.dispatch("event:user.github.linked");
-			});
-		});
-	}
-});
-
-CacheModule.runJob("SUB", {
-	channel: "user.unlinkGithub",
-	cb: userId => {
-		WSModule.runJob("SOCKETS_FROM_USER", { userId }).then(sockets => {
-			sockets.forEach(socket => {
-				socket.dispatch("event:user.github.unlinked");
-			});
-		});
-	}
-});
-
 CacheModule.runJob("SUB", {
 	channel: "user.ban",
 	cb: data => {
@@ -194,7 +160,6 @@ CacheModule.runJob("SUB", {
 				"name",
 				"username",
 				"avatar",
-				"services.github.id",
 				"role",
 				"email.address",
 				"email.verified",
@@ -202,7 +167,7 @@ CacheModule.runJob("SUB", {
 				"services.password.password"
 			],
 			(err, user) => {
-				const newUser = { ...user._doc, hasPassword: !!user.services.password.password };
+				const newUser = user._doc;
 				delete newUser.services.password;
 				WSModule.runJob("EMIT_TO_ROOMS", {
 					rooms: ["admin.users", `edit-user.${data.userId}`],
@@ -275,26 +240,8 @@ export default {
 									"services.password.password",
 									"services.password.reset.code",
 									"services.password.reset.expires",
-									"services.password.set.code",
-									"services.password.set.expires",
-									"services.github.access_token",
 									"email.verificationToken"
 								],
-								specialProperties: {
-									hasPassword: [
-										{
-											$addFields: {
-												hasPassword: {
-													$cond: [
-														{ $eq: [{ $type: "$services.password.password" }, "string"] },
-														true,
-														false
-													]
-												}
-											}
-										}
-									]
-								},
 								specialQueries: {}
 							},
 							this
@@ -440,8 +387,7 @@ export default {
 				// otherwise compare the requested password and the actual users password
 				(user, next) => {
 					if (!user) return next("User not found");
-					if (!user.services.password || !user.services.password.password)
-						return next("The account you are trying to access uses GitHub to log in.");
+					if (!user.services.password || !user.services.password.password) return next("Invalid password");
 
 					return bcrypt.compare(sha256(password), user.services.password.password, (err, match) => {
 						if (err) return next(err);
@@ -678,63 +624,6 @@ export default {
 		);
 	}),
 
-	/**
-	 * Checks if user's github access token has expired or not (ie. if their github account is still linked)
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {Function} cb - gets called with the result
-	 */
-	confirmGithubLink: isLoginRequired(async function confirmGithubLink(session, cb) {
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-
-		return async.waterfall(
-			[
-				next => {
-					if (!config.get("apis.github.enabled")) return next("GitHub authentication is disabled.");
-					return userModel.findOne({ _id: session.userId }, (err, user) => next(err, user));
-				},
-
-				(user, next) => {
-					if (!user.services.github) return next("You don't have GitHub linked to your account.");
-
-					return axios
-						.get(`https://api.github.com/user/emails`, {
-							headers: {
-								"User-Agent": "request",
-								Authorization: `token ${user.services.github.access_token}`
-							}
-						})
-						.then(res => next(null, res))
-						.catch(err => next(err));
-				},
-
-				(res, next) => next(null, res.status === 200)
-			],
-			async (err, linked) => {
-				if (err) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"USER_CONFIRM_GITHUB_LINK",
-						`Couldn't confirm github link for user "${session.userId}". "${err}"`
-					);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log(
-					"SUCCESS",
-					"USER_CONFIRM_GITHUB_LINK",
-					`GitHub is ${linked ? "linked" : "not linked"} for user "${session.userId}".`
-				);
-
-				return cb({
-					status: "success",
-					data: { linked },
-					message: "Successfully checked if GitHub accounty was linked."
-				});
-			}
-		);
-	}),
-
 	/**
 	 * Removes all sessions for a user
 	 * @param {object} session - the session object automatically added by the websocket
@@ -1364,9 +1253,7 @@ export default {
 							email: {
 								address: user.email.address,
 								verified: user.email.verified
-							},
-							hasPassword: !!user.services.password,
-							services: { github: user.services.github }
+							}
 						}
 					});
 				}
@@ -1447,7 +1334,6 @@ export default {
 				};
 
 				if (user.services.password && user.services.password.password) sanitisedUser.password = true;
-				if (user.services.github && user.services.github.id) sanitisedUser.github = true;
 				if (user.services.oidc && user.services.oidc.sub) sanitisedUser.oidc = true;
 
 				this.log("SUCCESS", "FIND_BY_SESSION", `User found. "${user.username}".`);
@@ -2001,313 +1887,6 @@ export default {
 		);
 	}),
 
-	/**
-	 * Requests a password for a session
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} email - the email of the user that requests a password reset
-	 * @param {Function} cb - gets called with the result
-	 */
-	requestPassword: isLoginRequired(async function requestPassword(session, cb) {
-		const code = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 8 }, this);
-		const passwordRequestSchema = await MailModule.runJob(
-			"GET_SCHEMA",
-			{
-				schemaName: "passwordRequest"
-			},
-			this
-		);
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		async.waterfall(
-			[
-				next => {
-					userModel.findOne({ _id: session.userId }, next);
-				},
-
-				(user, next) => {
-					if (!user) return next("User not found.");
-					if (user.services.password && user.services.password.password)
-						return next("You already have a password set.");
-					return next(null, user);
-				},
-
-				(user, next) => {
-					const expires = new Date();
-					expires.setDate(expires.getDate() + 1);
-					userModel.findOneAndUpdate(
-						{ "email.address": user.email.address },
-						{
-							$set: {
-								"services.password": {
-									set: { code, expires }
-								}
-							}
-						},
-						{ runValidators: true },
-						next
-					);
-				},
-
-				(user, next) => {
-					passwordRequestSchema(user.email.address, user.username, code, next);
-				}
-			],
-			async err => {
-				if (err && err !== true) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-
-					this.log(
-						"ERROR",
-						"REQUEST_PASSWORD",
-						`UserId '${session.userId}' failed to request password. '${err}'`
-					);
-
-					return cb({ status: "error", message: err });
-				}
-
-				this.log(
-					"SUCCESS",
-					"REQUEST_PASSWORD",
-					`UserId '${session.userId}' successfully requested a password.`
-				);
-
-				return cb({
-					status: "success",
-					message: "Successfully requested password."
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Verifies a password code
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} code - the password code
-	 * @param {Function} cb - gets called with the result
-	 */
-	verifyPasswordCode: isLoginRequired(async function verifyPasswordCode(session, code, cb) {
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		async.waterfall(
-			[
-				next => {
-					if (!code || typeof code !== "string") return next("Invalid code.");
-					return userModel.findOne(
-						{
-							"services.password.set.code": code,
-							_id: session.userId
-						},
-						next
-					);
-				},
-
-				(user, next) => {
-					if (!user) return next("Invalid code.");
-					if (user.services.password.set.expires < new Date()) return next("That code has expired.");
-					return next(null);
-				}
-			],
-			async err => {
-				if (err && err !== true) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "VERIFY_PASSWORD_CODE", `Code '${code}' failed to verify. '${err}'`);
-					cb({ status: "error", message: err });
-				} else {
-					this.log("SUCCESS", "VERIFY_PASSWORD_CODE", `Code '${code}' successfully verified.`);
-					cb({
-						status: "success",
-						message: "Successfully verified password code."
-					});
-				}
-			}
-		);
-	}),
-
-	/**
-	 * Adds a password to a user with a code
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {string} code - the password code
-	 * @param {string} newPassword - the new password code
-	 * @param {Function} cb - gets called with the result
-	 */
-	changePasswordWithCode: isLoginRequired(async function changePasswordWithCode(session, code, newPassword, cb) {
-		const userModel = await DBModule.runJob(
-			"GET_MODEL",
-			{
-				modelName: "user"
-			},
-			this
-		);
-		async.waterfall(
-			[
-				next => {
-					if (!code || typeof code !== "string") return next("Invalid code.");
-					return userModel.findOne({ "services.password.set.code": code }, next);
-				},
-
-				(user, next) => {
-					if (!user) return next("Invalid code.");
-					if (!user.services.password.set.expires > new Date()) return next("That code has expired.");
-					return next();
-				},
-
-				next => {
-					if (!DBModule.passwordValid(newPassword))
-						return next("Invalid password. Check if it meets all the requirements.");
-					return next();
-				},
-
-				next => {
-					bcrypt.genSalt(10, next);
-				},
-
-				// hash the password
-				(salt, next) => {
-					bcrypt.hash(sha256(newPassword), salt, next);
-				},
-
-				(hashedPassword, next) => {
-					userModel.updateOne(
-						{ "services.password.set.code": code },
-						{
-							$set: {
-								"services.password.password": hashedPassword
-							},
-							$unset: { "services.password.set": "" }
-						},
-						{ runValidators: true },
-						next
-					);
-				}
-			],
-			async err => {
-				if (err && err !== true) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "ADD_PASSWORD_WITH_CODE", `Code '${code}' failed to add password. '${err}'`);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log("SUCCESS", "ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
-
-				CacheModule.runJob("PUB", {
-					channel: "user.linkPassword",
-					value: session.userId
-				});
-
-				CacheModule.runJob("PUB", {
-					channel: "user.updated",
-					value: { userId: session.userId }
-				});
-
-				return cb({
-					status: "success",
-					message: "Successfully added password."
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Unlinks password from user
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {Function} cb - gets called with the result
-	 */
-	unlinkPassword: isLoginRequired(async function unlinkPassword(session, cb) {
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		async.waterfall(
-			[
-				next => {
-					userModel.findOne({ _id: session.userId }, next);
-				},
-
-				(user, next) => {
-					if (!user) return next("Not logged in.");
-					if (!config.get("apis.github.enabled")) return next("Unlinking password is disabled.");
-					if (!user.services.github || !user.services.github.id)
-						return next("You can't remove password login without having GitHub login.");
-					return userModel.updateOne({ _id: session.userId }, { $unset: { "services.password": "" } }, next);
-				}
-			],
-			async err => {
-				if (err && err !== true) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"UNLINK_PASSWORD",
-						`Unlinking password failed for userId '${session.userId}'. '${err}'`
-					);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log("SUCCESS", "UNLINK_PASSWORD", `Unlinking password successful for userId '${session.userId}'.`);
-
-				CacheModule.runJob("PUB", {
-					channel: "user.unlinkPassword",
-					value: session.userId
-				});
-
-				CacheModule.runJob("PUB", {
-					channel: "user.updated",
-					value: { userId: session.userId }
-				});
-
-				return cb({
-					status: "success",
-					message: "Successfully unlinked password."
-				});
-			}
-		);
-	}),
-
-	/**
-	 * Unlinks GitHub from user
-	 * @param {object} session - the session object automatically added by the websocket
-	 * @param {Function} cb - gets called with the result
-	 */
-	unlinkGitHub: isLoginRequired(async function unlinkGitHub(session, cb) {
-		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
-		async.waterfall(
-			[
-				next => {
-					userModel.findOne({ _id: session.userId }, next);
-				},
-
-				(user, next) => {
-					if (!user) return next("Not logged in.");
-					if (!user.services.password || !user.services.password.password)
-						return next("You can't remove GitHub login without having password login.");
-					return userModel.updateOne({ _id: session.userId }, { $unset: { "services.github": "" } }, next);
-				}
-			],
-			async err => {
-				if (err && err !== true) {
-					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log(
-						"ERROR",
-						"UNLINK_GITHUB",
-						`Unlinking GitHub failed for userId '${session.userId}'. '${err}'`
-					);
-					return cb({ status: "error", message: err });
-				}
-
-				this.log("SUCCESS", "UNLINK_GITHUB", `Unlinking GitHub successful for userId '${session.userId}'.`);
-
-				CacheModule.runJob("PUB", {
-					channel: "user.unlinkGithub",
-					value: session.userId
-				});
-
-				CacheModule.runJob("PUB", {
-					channel: "user.updated",
-					value: { userId: session.userId }
-				});
-
-				return cb({
-					status: "success",
-					message: "Successfully unlinked GitHub."
-				});
-			}
-		);
-	}),
-
 	/**
 	 * Requests a password reset for an email
 	 * @param {object} session - the session object automatically added by the websocket
@@ -2336,8 +1915,6 @@ export default {
 
 				(user, next) => {
 					if (!user) return next("User not found.");
-					if (!user.services.password || !user.services.password.password)
-						return next("User does not have a password set, and probably uses GitHub to log in.");
 					return next(null, user);
 				},
 
@@ -2412,8 +1989,6 @@ export default {
 
 					(user, next) => {
 						if (!user) return next("User not found.");
-						if (!user.services.password || !user.services.password.password)
-							return next("User does not have a password set, and probably uses GitHub to log in.");
 						return next();
 					},
 

+ 0 - 92
backend/logic/app.js

@@ -51,98 +51,6 @@ class _AppModule extends CoreClass {
 			res.redirect(`${appUrl}?err=${encodeURIComponent(err)}`);
 		}
 
-		if (config.get("apis.github.enabled")) {
-			const redirectUri =
-				config.get("apis.github.redirect_uri").length > 0
-					? config.get("apis.github.redirect_uri")
-					: `${appUrl}/backend/auth/github/authorize/callback`;
-
-			app.get("/auth/github/authorize", async (req, res) => {
-				if (this.getStatus() !== "READY") {
-					this.log(
-						"INFO",
-						"APP_REJECTED_GITHUB_AUTHORIZE",
-						`A user tried to use github authorize, but the APP module is currently not ready.`
-					);
-					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
-				}
-
-				const params = [
-					`client_id=${config.get("apis.github.client")}`,
-					`redirect_uri=${redirectUri}`,
-					`scope=user:email`
-				].join("&");
-				return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
-			});
-
-			app.get("/auth/github/link", async (req, res) => {
-				if (this.getStatus() !== "READY") {
-					this.log(
-						"INFO",
-						"APP_REJECTED_GITHUB_AUTHORIZE",
-						`A user tried to use github authorize, but the APP module is currently not ready.`
-					);
-					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
-				}
-
-				const params = [
-					`client_id=${config.get("apis.github.client")}`,
-					`redirect_uri=${redirectUri}`,
-					`scope=user:email`,
-					`state=${req.cookies[SIDname]}` // TODO don't do this
-				].join("&");
-				return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
-			});
-
-			app.get("/auth/github/authorize/callback", async (req, res) => {
-				if (this.getStatus() !== "READY") {
-					this.log(
-						"INFO",
-						"APP_REJECTED_GITHUB_AUTHORIZE",
-						`A user tried to use github authorize, but the APP module is currently not ready.`
-					);
-
-					redirectOnErr(res, "Something went wrong on our end. Please try again later.");
-					return;
-				}
-
-				const { code, state, error, error_description: errorDescription } = req.query;
-
-				// GITHUB_AUTHORIZE_CALLBACK job handles login/register/linking
-				UsersModule.runJob("GITHUB_AUTHORIZE_CALLBACK", { code, state, error, errorDescription })
-					.then(({ redirectUrl, sessionId, userId }) => {
-						if (sessionId) {
-							const date = new Date();
-							date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
-
-							res.cookie(SIDname, sessionId, {
-								expires: date,
-								secure: config.get("url.secure"),
-								path: "/",
-								domain: config.get("url.host")
-							});
-
-							this.log(
-								"INFO",
-								"AUTH_GITHUB_AUTHORIZE_CALLBACK",
-								`User "${userId}" successfully authorized with GitHub.`
-							);
-						}
-
-						res.redirect(redirectUrl);
-					})
-					.catch(err => {
-						this.log(
-							"ERROR",
-							"AUTH_GITHUB_AUTHORIZE_CALLBACK",
-							`Failed to authorize with GitHub. "${err.message}"`
-						);
-
-						return redirectOnErr(res, err.message);
-					});
-			});
-		}
-
 		if (config.get("apis.oidc.enabled")) {
 			app.get("/auth/oidc/authorize", async (req, res) => {
 				if (this.getStatus() !== "READY") {

+ 0 - 8
backend/logic/db/schemas/user.js

@@ -19,16 +19,8 @@ export default {
 			reset: {
 				code: { type: String, min: 8, max: 8 },
 				expires: { type: Date }
-			},
-			set: {
-				code: { type: String, min: 8, max: 8 },
-				expires: { type: Date }
 			}
 		},
-		github: {
-			id: Number,
-			access_token: String
-		},
 		oidc: {
 			sub: String,
 			access_token: String

+ 2 - 263
backend/logic/users.js

@@ -1,12 +1,9 @@
 import config from "config";
-import oauth from "oauth";
 import axios from "axios";
 import bcrypt from "bcrypt";
 import sha256 from "sha256";
 import CoreClass from "../core";
 
-const { OAuth2 } = oauth;
-
 let UsersModule;
 let MailModule;
 let CacheModule;
@@ -55,19 +52,6 @@ class _UsersModule extends CoreClass {
 		});
 
 		this.appUrl = `${config.get("url.secure") ? "https" : "http"}://${config.get("url.host")}`;
-		this.githubRedirectUri =
-			config.get("apis.github.redirect_uri").length > 0
-				? config.get("apis.github.redirect_uri")
-				: `${this.appUrl}/backend/auth/github/authorize/callback`;
-
-		this.oauth2 = new OAuth2(
-			config.get("apis.github.client"),
-			config.get("apis.github.secret"),
-			"https://github.com/",
-			"login/oauth/authorize",
-			"login/oauth/access_token",
-			null
-		);
 
 		// getOAuthAccessToken uses callbacks by default, so make a helper function to turn it into a promise instead
 		this.getOAuthAccessToken = (...args) =>
@@ -238,249 +222,6 @@ class _UsersModule extends CoreClass {
 		);
 	}
 
-	/**
-	 * Handles callback route being accessed, which has data from GitHub during the oauth process
-	 * Will be used to either log the user in, register the user, or link the GitHub account to an existing account
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.code - code we need to use to get the access token
-	 * @param {string} payload.state - custom state we may have passed to GitHub during the first step
-	 * @param {string} payload.error - error code if an error occured
-	 * @param {string} payload.errorDescription - error description if an error occured
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	async GITHUB_AUTHORIZE_CALLBACK(payload) {
-		const { code, state, error, errorDescription } = payload;
-		if (error) throw new Error(errorDescription);
-
-		// Tries to get access token. We don't use the refresh token currently
-		const { accessToken, /* refreshToken, */ results } = await UsersModule.getOAuthAccessToken(code, {
-			redirect_uri: UsersModule.githubRedirectUri
-		});
-		if (!accessToken) throw new Error(results.error_description);
-
-		const options = {
-			headers: {
-				"User-Agent": "request",
-				Authorization: `token ${accessToken}`
-			}
-		};
-		// Gets user data
-		const githubUserData = await axios.get("https://api.github.com/user", options);
-		if (githubUserData.status !== 200) throw new Error(githubUserData.data.message);
-		if (!githubUserData.data.id) throw new Error("Something went wrong, no id.");
-
-		// If we specified a state in the first step when we redirected the user to GitHub, it was to link a
-		// GitHub account to an existing Musare account, so continue with a job specifically for linking the account
-		if (state)
-			return UsersModule.runJob(
-				"GITHUB_AUTHORIZE_CALLBACK_LINK",
-				{ state, githubId: githubUserData.data.id, accessToken },
-				this
-			);
-
-		const user = await UsersModule.userModel.findOne({ "services.github.id": githubUserData.data.id });
-		let userId;
-		if (user) {
-			// Refresh access token, though it's pretty useless as it'll probably expire and then be useless,
-			// and we don't use it afterwards at all
-			user.services.github.access_token = accessToken;
-			await user.save();
-			userId = user._id;
-		} else {
-			// Try to register the user. Will throw an error if it's unable to do so or any error occurs
-			({ userId } = await UsersModule.runJob(
-				"GITHUB_AUTHORIZE_CALLBACK_REGISTER",
-				{ githubUserData, accessToken },
-				this
-			));
-		}
-
-		// Create session for the userId gotten above, as the user existed or was successfully registered
-		const sessionId = await UtilsModule.runJob("GUID", {}, this);
-		await CacheModule.runJob(
-			"HSET",
-			{
-				table: "sessions",
-				key: sessionId,
-				value: UsersModule.sessionSchema(sessionId, userId)
-			},
-			this
-		);
-
-		return { sessionId, userId, redirectUrl: UsersModule.appUrl };
-	}
-
-	/**
-	 * Handles registering the user in the GitHub login/register/link callback/process
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.githubUserData - data we got from the /user API endpoint from GitHub
-	 * @param {string} payload.accessToken - access token for the GitHub user
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	async GITHUB_AUTHORIZE_CALLBACK_REGISTER(payload) {
-		const { githubUserData, accessToken } = payload;
-		let user;
-
-		// Check if username already exists
-		user = await UsersModule.userModel.findOne({ username: new RegExp(`^${githubUserData.data.login}$`, "i") });
-		if (user) throw new Error(`An account with that username already exists.`);
-
-		// Get emails used for GitHub account
-		const githubEmailsData = await axios.get("https://api.github.com/user/emails", {
-			headers: {
-				"User-Agent": "request",
-				Authorization: `token ${accessToken}`
-			}
-		});
-		if (!Array.isArray(githubEmailsData.data)) throw new Error(githubEmailsData.message);
-
-		const primaryEmailAddress = githubEmailsData.data.find(emailAddress => emailAddress.primary)?.email;
-		if (!primaryEmailAddress) throw new Error("No primary email address found.");
-
-		user = await UsersModule.userModel.findOne({ "email.address": primaryEmailAddress });
-		if (user && Object.keys(JSON.parse(user.services.github)).length === 0)
-			throw new Error(`An account with that email address exists, but is not linked to GitHub.`);
-		if (user) throw new Error(`An account with that email address already exists.`);
-
-		const userId = await UtilsModule.runJob(
-			"GENERATE_RANDOM_STRING",
-			{
-				length: 12
-			},
-			this
-		);
-		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this);
-		const gravatarUrl = await UtilsModule.runJob(
-			"CREATE_GRAVATAR",
-			{
-				email: primaryEmailAddress
-			},
-			this
-		);
-		const likedSongsPlaylist = await PlaylistsModule.runJob(
-			"CREATE_USER_PLAYLIST",
-			{
-				userId,
-				displayName: "Liked Songs",
-				type: "user-liked"
-			},
-			this
-		);
-		const dislikedSongsPlaylist = await PlaylistsModule.runJob(
-			"CREATE_USER_PLAYLIST",
-			{
-				userId,
-				displayName: "Disliked Songs",
-				type: "user-disliked"
-			},
-			this
-		);
-
-		user = {
-			_id: userId,
-			username: githubUserData.data.login,
-			name: githubUserData.data.name,
-			location: githubUserData.data.location,
-			bio: githubUserData.data.bio,
-			email: {
-				address: primaryEmailAddress,
-				verificationToken
-			},
-			services: {
-				github: {
-					id: githubUserData.data.id,
-					access_token: accessToken
-				}
-			},
-			avatar: {
-				type: "gravatar",
-				url: gravatarUrl
-			},
-			likedSongsPlaylist,
-			dislikedSongsPlaylist
-		};
-
-		await UsersModule.userModel.create(user);
-
-		await UsersModule.verifyEmailSchema(primaryEmailAddress, githubUserData.data.login, verificationToken);
-		await ActivitiesModule.runJob(
-			"ADD_ACTIVITY",
-			{
-				userId,
-				type: "user__joined",
-				payload: { message: "Welcome to Musare!" }
-			},
-			this
-		);
-
-		return {
-			userId
-		};
-	}
-
-	/**
-	 * Job to attempt to link a GitHub user to a Musare account
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.state - state we passed to GitHub and got back from GitHub
-	 * @param {string} payload.githubId - GitHub user id
-	 * @param {string} payload.accessToken - GitHub user access token
-	 * @returns {Promise} - returns a promise (resolve, reject)
-	 */
-	async GITHUB_AUTHORIZE_CALLBACK_LINK(payload) {
-		const { state, githubId, accessToken } = payload;
-
-		// State is currently the session id (SID), so check if that session (still) exists
-		const session = await CacheModule.runJob(
-			"HGET",
-			{
-				table: "sessions",
-				key: state
-			},
-			this
-		);
-		if (!session) throw new Error("Invalid session.");
-
-		const user = await UsersModule.userModel.findOne({ _id: session.userId });
-		if (!user) throw new Error("User not found.");
-		if (user.services.github && user.services.github.id) throw new Error("Account already has GitHub linked.");
-
-		const { _id: userId } = user;
-
-		await UsersModule.userModel.updateOne(
-			{ _id: userId },
-			{
-				$set: {
-					"services.github": {
-						id: githubId,
-						access_token: accessToken
-					}
-				}
-			},
-			{ runValidators: true }
-		);
-
-		await CacheModule.runJob(
-			"PUB",
-			{
-				channel: "user.linkGithub",
-				value: userId
-			},
-			this
-		);
-		await CacheModule.runJob(
-			"PUB",
-			{
-				channel: "user.updated",
-				value: { userId }
-			},
-			this
-		);
-
-		return {
-			redirectUrl: `${UsersModule.appUrl}/settings?tab=security`
-		};
-	}
-
 	/**
 	 * Handles callback route being accessed, which has data from OIDC during the oauth process
 	 * Will be used to either log the user in or register the user
@@ -553,10 +294,10 @@ class _UsersModule extends CoreClass {
 	}
 
 	/**
-	 * Handles registering the user in the GitHub login/register/link callback/process
+	 * Handles registering the user in the OIDC login/register/link callback/process
 	 * @param {object} payload - object that contains the payload
 	 * @param {string} payload.userInfoResponse - data we got from the OIDC user info API endpoint
-	 * @param {string} payload.accessToken - access token for the GitHub user
+	 * @param {string} payload.accessToken - access token for the OIDC user
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	async OIDC_AUTHORIZE_CALLBACK_REGISTER(payload) {
@@ -573,8 +314,6 @@ class _UsersModule extends CoreClass {
 		if (!emailAddress) throw new Error("No email address found.");
 
 		user = await UsersModule.userModel.findOne({ "email.address": emailAddress });
-		if (user && Object.keys(JSON.parse(user.services.github)).length === 0)
-			throw new Error(`An account with that email address already exists, but is not linked to OIDC.`);
 		if (user) throw new Error(`An account with that email address already exists.`);
 
 		const userId = await UtilsModule.runJob(

+ 0 - 1
backend/logic/ws.js

@@ -575,7 +575,6 @@ class _WSModule extends CoreClass {
 						enabled: config.get("apis.recaptcha.enabled"),
 						key: config.get("apis.recaptcha.key")
 					},
-					githubAuthentication: config.get("apis.github.enabled") && !config.get("apis.oidc.enabled"),
 					oidcAuthentication: config.get("apis.oidc.enabled"),
 					messages: config.get("messages"),
 					christmas: config.get("christmas"),

+ 0 - 7
frontend/src/App.vue

@@ -247,13 +247,6 @@ onMounted(async () => {
 		});
 
 		router.isReady().then(() => {
-			if (
-				configStore.githubAuthentication &&
-				localStorage.getItem("github_redirect")
-			) {
-				router.push(localStorage.getItem("github_redirect"));
-				localStorage.removeItem("github_redirect");
-			}
 			if (
 				configStore.oidcAuthentication &&
 				localStorage.getItem("oidc_redirect")

+ 1 - 30
frontend/src/components/modals/Login.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { defineAsyncComponent, ref } from "vue";
-import { useRoute } from "vue-router";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useConfigStore } from "@/stores/config";
@@ -9,8 +8,6 @@ import { useModalsStore } from "@/stores/modals";
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 
-const route = useRoute();
-
 const email = ref("");
 const password = ref({
 	value: "",
@@ -19,7 +16,7 @@ const password = ref({
 const passwordElement = ref();
 
 const configStore = useConfigStore();
-const { githubAuthentication, registrationDisabled } = storeToRefs(configStore);
+const { registrationDisabled } = storeToRefs(configStore);
 const { login } = useUserAuthStore();
 
 const { openModal, closeCurrentModal } = useModalsStore();
@@ -62,10 +59,6 @@ const changeToRegisterModal = () => {
 	closeCurrentModal();
 	openModal("register");
 };
-
-const githubRedirect = () => {
-	localStorage.setItem("github_redirect", route.path);
-};
 </script>
 
 <template>
@@ -150,20 +143,6 @@ const githubRedirect = () => {
 					<button class="button is-primary" @click="submitModal()">
 						Login
 					</button>
-					<a
-						v-if="githubAuthentication"
-						class="button is-github"
-						:href="configStore.urls.api + '/auth/github/authorize'"
-						@click="githubRedirect()"
-					>
-						<div class="icon">
-							<img
-								class="invert"
-								src="/assets/social/github.svg"
-							/>
-						</div>
-						&nbsp;&nbsp;Login with GitHub
-					</a>
 				</div>
 
 				<p
@@ -204,14 +183,6 @@ const githubRedirect = () => {
 	}
 }
 
-.button.is-github {
-	background-color: var(--dark-grey-2);
-	color: var(--white) !important;
-}
-
-.is-github:focus {
-	background-color: var(--dark-grey-4);
-}
 .is-primary:focus {
 	background-color: var(--primary-color) !important;
 }

+ 1 - 32
frontend/src/components/modals/Register.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { defineAsyncComponent, ref, watch, onMounted } from "vue";
-import { useRoute } from "vue-router";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useConfigStore } from "@/stores/config";
@@ -13,8 +12,6 @@ const InputHelpBox = defineAsyncComponent(
 	() => import("@/components/InputHelpBox.vue")
 );
 
-const route = useRoute();
-
 const username = ref({
 	value: "",
 	valid: false,
@@ -41,8 +38,7 @@ const passwordElement = ref();
 const { register } = useUserAuthStore();
 
 const configStore = useConfigStore();
-const { registrationDisabled, recaptcha, githubAuthentication } =
-	storeToRefs(configStore);
+const { registrationDisabled, recaptcha } = storeToRefs(configStore);
 const { openModal, closeCurrentModal } = useModalsStore();
 
 const submitModal = () => {
@@ -76,10 +72,6 @@ const changeToLoginModal = () => {
 	openModal("login");
 };
 
-const githubRedirect = () => {
-	localStorage.setItem("github_redirect", route.path);
-};
-
 watch(
 	() => username.value.value,
 	value => {
@@ -274,20 +266,6 @@ onMounted(async () => {
 					<button class="button is-primary" @click="submitModal()">
 						Register
 					</button>
-					<a
-						v-if="githubAuthentication"
-						class="button is-github"
-						:href="configStore.urls.api + '/auth/github/authorize'"
-						@click="githubRedirect()"
-					>
-						<div class="icon">
-							<img
-								class="invert"
-								src="/assets/social/github.svg"
-							/>
-						</div>
-						&nbsp;&nbsp;Register with GitHub
-					</a>
 				</div>
 
 				<p class="content-box-optional-helper">
@@ -329,15 +307,6 @@ onMounted(async () => {
 	}
 }
 
-.button.is-github {
-	background-color: var(--dark-grey-2);
-	color: var(--white) !important;
-}
-
-.is-github:focus {
-	background-color: var(--dark-grey-4);
-}
-
 .invert {
 	filter: brightness(5);
 }

+ 8 - 111
frontend/src/components/modals/RemoveAccount.vue

@@ -1,10 +1,8 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onMounted } from "vue";
-import { useRoute } from "vue-router";
+import { defineAsyncComponent, ref } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useConfigStore } from "@/stores/config";
-import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 
@@ -13,21 +11,15 @@ const QuickConfirm = defineAsyncComponent(
 	() => import("@/components/QuickConfirm.vue")
 );
 
-const props = defineProps({
-	modalUuid: { type: String, required: true },
-	githubLinkConfirmed: { type: Boolean, default: false }
+defineProps({
+	modalUuid: { type: String, required: true }
 });
 
 const configStore = useConfigStore();
-const { cookie, githubAuthentication, oidcAuthentication, messages } =
-	storeToRefs(configStore);
-const settingsStore = useSettingsStore();
-const route = useRoute();
+const { cookie, oidcAuthentication, messages } = storeToRefs(configStore);
 
 const { socket } = useWebsocketsStore();
 
-const { isPasswordLinked, isGithubLinked, isOIDCLinked } = settingsStore;
-
 const { closeCurrentModal } = useModalsStore();
 
 const step = ref("confirm-identity");
@@ -68,33 +60,11 @@ const confirmPasswordMatch = () =>
 		else new Toast(res.message);
 	});
 
-const confirmGithubLink = () =>
-	socket.dispatch("users.confirmGithubLink", res => {
-		if (res.status === "success") {
-			if (res.data.linked) step.value = "remove-account";
-			else {
-				new Toast(
-					`Your GitHub account isn't linked. Please re-link your account and try again.`
-				);
-				step.value = "relink-github";
-			}
-		} else new Toast(res.message);
-	});
-
 const confirmOIDCLink = () => {
 	// TODO
 	step.value = "remove-account";
 };
 
-const relinkGithub = () => {
-	localStorage.setItem(
-		"github_redirect",
-		`${window.location.pathname + window.location.search}${
-			!route.query.removeAccount ? "&removeAccount=relinked-github" : ""
-		}`
-	);
-};
-
 const remove = () =>
 	socket.dispatch("users.remove", res => {
 		if (res.status === "success") {
@@ -107,10 +77,6 @@ const remove = () =>
 
 		return new Toast(res.message);
 	});
-
-onMounted(async () => {
-	if (props.githubLinkConfirmed === true) confirmGithubLink();
-});
 </script>
 
 <template>
@@ -130,9 +96,7 @@ onMounted(async () => {
 				<p
 					class="step"
 					:class="{
-						selected:
-							(isPasswordLinked && step === 'export-data') ||
-							step === 'relink-github'
+						selected: step === 'export-data'
 					}"
 				>
 					2
@@ -141,27 +105,17 @@ onMounted(async () => {
 				<p
 					class="step"
 					:class="{
-						selected:
-							(isPasswordLinked && step === 'remove-account') ||
-							step === 'export-data'
+						selected: step === 'remove-account'
 					}"
 				>
 					3
 				</p>
-				<span class="divider" v-if="!isPasswordLinked"></span>
-				<p
-					class="step"
-					:class="{ selected: step === 'remove-account' }"
-					v-if="!isPasswordLinked"
-				>
-					4
-				</p>
 			</div>
 
 			<div
 				class="content-box"
 				id="password-linked"
-				v-if="step === 'confirm-identity' && isPasswordLinked"
+				v-if="!oidcAuthentication && step === 'confirm-identity'"
 			>
 				<h2 class="content-box-title">Enter your password</h2>
 				<p class="content-box-description">
@@ -221,37 +175,7 @@ onMounted(async () => {
 
 			<div
 				class="content-box"
-				v-else-if="
-					githubAuthentication &&
-					isGithubLinked &&
-					step === 'confirm-identity'
-				"
-			>
-				<h2 class="content-box-title">Verify your GitHub</h2>
-				<p class="content-box-description">
-					Check your account is still linked to remove your account.
-				</p>
-
-				<div class="content-box-inputs">
-					<a class="button is-github" @click="confirmGithubLink()">
-						<div class="icon">
-							<img
-								class="invert"
-								src="/assets/social/github.svg"
-							/>
-						</div>
-						&nbsp; Check GitHub is linked
-					</a>
-				</div>
-			</div>
-
-			<div
-				class="content-box"
-				v-else-if="
-					oidcAuthentication &&
-					isOIDCLinked &&
-					step === 'confirm-identity'
-				"
+				v-else-if="oidcAuthentication && step === 'confirm-identity'"
 			>
 				<h2 class="content-box-title">Verify your OIDC</h2>
 				<p class="content-box-description">
@@ -271,33 +195,6 @@ onMounted(async () => {
 				</div>
 			</div>
 
-			<div
-				class="content-box"
-				v-if="githubAuthentication && step === 'relink-github'"
-			>
-				<h2 class="content-box-title">Re-link GitHub</h2>
-				<p class="content-box-description">
-					Re-link your GitHub account in order to verify your
-					identity.
-				</p>
-
-				<div class="content-box-inputs">
-					<a
-						class="button is-github"
-						@click="relinkGithub()"
-						:href="`${configStore.urls.api}/auth/github/link`"
-					>
-						<div class="icon">
-							<img
-								class="invert"
-								src="/assets/social/github.svg"
-							/>
-						</div>
-						&nbsp; Re-link GitHub to account
-					</a>
-				</div>
-			</div>
-
 			<div v-if="step === 'export-data'">
 				DOWNLOAD A BACKUP OF YOUR DATA BEFORE ITS PERMENATNELY DELETED
 			</div>

+ 0 - 9
frontend/src/main.ts

@@ -140,15 +140,6 @@ const router = createRouter({
 				configRequired: "mailEnabled"
 			}
 		},
-		{
-			path: "/set_password",
-			props: { mode: "set" },
-			component: () => import("@/pages/ResetPassword.vue"),
-			meta: {
-				configRequired: "mailEnabled",
-				loginRequired: true
-			}
-		},
 		{
 			path: "/admin",
 			name: "admin",

+ 0 - 40
frontend/src/pages/Admin/Users/index.vue

@@ -63,14 +63,6 @@ const columns = ref<TableColumn[]>([
 		minWidth: 230,
 		defaultWidth: 230
 	},
-	{
-		name: "githubId",
-		displayName: "GitHub ID",
-		properties: ["services.github.id"],
-		sortProperty: "services.github.id",
-		minWidth: 115,
-		defaultWidth: 115
-	},
 	{
 		name: "oidcSub",
 		displayName: "OIDC sub",
@@ -79,12 +71,6 @@ const columns = ref<TableColumn[]>([
 		minWidth: 115,
 		defaultWidth: 115
 	},
-	{
-		name: "hasPassword",
-		displayName: "Has Password",
-		properties: ["hasPassword"],
-		sortProperty: "hasPassword"
-	},
 	{
 		name: "role",
 		displayName: "Role",
@@ -140,13 +126,6 @@ const filters = ref<TableFilter[]>([
 		filterTypes: ["contains", "exact", "regex"],
 		defaultFilterType: "contains"
 	},
-	{
-		name: "githubId",
-		displayName: "GitHub ID",
-		property: "services.github.id",
-		filterTypes: ["contains", "exact", "regex"],
-		defaultFilterType: "contains"
-	},
 	{
 		name: "oidcSub",
 		displayName: "OIDC sub",
@@ -154,13 +133,6 @@ const filters = ref<TableFilter[]>([
 		filterTypes: ["contains", "exact", "regex"],
 		defaultFilterType: "contains"
 	},
-	{
-		name: "hasPassword",
-		displayName: "Has Password",
-		property: "hasPassword",
-		filterTypes: ["boolean"],
-		defaultFilterType: "boolean"
-	},
 	{
 		name: "role",
 		displayName: "Role",
@@ -294,13 +266,6 @@ onMounted(() => {
 					slotProps.item._id
 				}}</span>
 			</template>
-			<template #column-githubId="slotProps">
-				<span
-					v-if="slotProps.item.services.github"
-					:title="slotProps.item.services.github.id"
-					>{{ slotProps.item.services.github.id }}</span
-				>
-			</template>
 			<template #column-oidcSub="slotProps">
 				<span
 					v-if="slotProps.item.services.oidc"
@@ -308,11 +273,6 @@ onMounted(() => {
 					>{{ slotProps.item.services.oidc.sub }}</span
 				>
 			</template>
-			<template #column-hasPassword="slotProps">
-				<span :title="slotProps.item.hasPassword">{{
-					slotProps.item.hasPassword
-				}}</span>
-			</template>
 			<template #column-role="slotProps">
 				<span :title="slotProps.item.role">{{
 					slotProps.item.role

+ 9 - 30
frontend/src/pages/ResetPassword.vue

@@ -16,10 +16,6 @@ const InputHelpBox = defineAsyncComponent(
 	() => import("@/components/InputHelpBox.vue")
 );
 
-const props = defineProps({
-	mode: { type: String, enum: ["reset", "set"], default: "reset" }
-});
-
 const userAuthStore = useUserAuthStore();
 const { email: accountEmail } = storeToRefs(userAuthStore);
 
@@ -89,13 +85,6 @@ const submitEmail = () => {
 
 	inputs.value.email.hasBeenSentAlready = false;
 
-	if (props.mode === "set") {
-		return socket.dispatch("users.requestPassword", res => {
-			new Toast(res.message);
-			if (res.status === "success") step.value = 2;
-		});
-	}
-
 	return socket.dispatch(
 		"users.requestPasswordReset",
 		inputs.value.email.value,
@@ -112,16 +101,10 @@ const submitEmail = () => {
 const verifyCode = () => {
 	if (!code.value) return new Toast("Code cannot be empty");
 
-	return socket.dispatch(
-		props.mode === "set"
-			? "users.verifyPasswordCode"
-			: "users.verifyPasswordResetCode",
-		code.value,
-		res => {
-			new Toast(res.message);
-			if (res.status === "success") step.value = 3;
-		}
-	);
+	return socket.dispatch("users.verifyPasswordResetCode", code.value, res => {
+		new Toast(res.message);
+		if (res.status === "success") step.value = 3;
+	});
 };
 
 const changePassword = () => {
@@ -132,9 +115,7 @@ const changePassword = () => {
 		return new Toast("Please enter a valid password.");
 
 	return socket.dispatch(
-		props.mode === "set"
-			? "users.changePasswordWithCode"
-			: "users.changePasswordWithResetCode",
+		"users.changePasswordWithResetCode",
 		code.value,
 		inputs.value.password.value,
 		res => {
@@ -199,14 +180,12 @@ onMounted(() => {
 
 <template>
 	<div>
-		<page-metadata
-			:title="mode === 'reset' ? 'Reset password' : 'Set password'"
-		/>
+		<page-metadata title="Reset password" />
 		<main-header />
 		<div class="container">
 			<div class="content-wrapper">
 				<h1 id="title" class="has-text-centered page-title">
-					{{ mode === "reset" ? "Reset" : "Set" }} your password
+					Reset your password
 				</h1>
 
 				<div id="steps">
@@ -466,7 +445,7 @@ onMounted(() => {
 								<i class="material-icons success-icon"
 									>check_circle</i
 								>
-								<h2>Password successfully {{ mode }}</h2>
+								<h2>Password successfully reset</h2>
 								<router-link
 									class="button is-dark"
 									to="/settings"
@@ -483,7 +462,7 @@ onMounted(() => {
 							>
 								<i class="material-icons error-icon">error</i>
 								<h2>
-									Password {{ mode }} failed, please try again
+									Password reset failed, please try again
 									later
 								</h2>
 								<router-link

+ 2 - 16
frontend/src/pages/Settings/Tabs/Account.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, watch, reactive, onMounted } from "vue";
-import { useRoute } from "vue-router";
+import { defineAsyncComponent, ref, watch, reactive } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useSettingsStore } from "@/stores/settings";
@@ -22,7 +21,6 @@ const QuickConfirm = defineAsyncComponent(
 
 const settingsStore = useSettingsStore();
 const userAuthStore = useUserAuthStore();
-const route = useRoute();
 const configStore = useConfigStore();
 
 const { socket } = useWebsocketsStore();
@@ -147,18 +145,6 @@ const removeActivities = () => {
 	});
 };
 
-onMounted(() => {
-	if (
-		route.query.removeAccount === "relinked-github" &&
-		!localStorage.getItem("github_redirect")
-	) {
-		openModal({
-			modal: "removeAccount",
-			props: { githubLinkConfirmed: true }
-		});
-	}
-});
-
 watch(
 	() => modifiedUser.username,
 	value => {
@@ -170,7 +156,7 @@ watch(
 			validation.username.valid = false;
 		} else if (
 			!_validation.regex.azAZ09_.test(value) &&
-			value !== originalUser.username // Sometimes a username pulled from GitHub won't succeed validation
+			value !== originalUser.username // Sometimes a username pulled from OIDC won't succeed validation
 		) {
 			validation.username.message =
 				"Invalid format. Allowed characters: a-z, A-Z, 0-9 and _.";

+ 74 - 160
frontend/src/pages/Settings/Tabs/Security.vue

@@ -3,7 +3,6 @@ import { defineAsyncComponent, ref, watch, reactive } from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useConfigStore } from "@/stores/config";
-import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import _validation from "@/validation";
@@ -16,9 +15,7 @@ const QuickConfirm = defineAsyncComponent(
 );
 
 const configStore = useConfigStore();
-const { githubAuthentication, sitename, oidcAuthentication } =
-	storeToRefs(configStore);
-const settingsStore = useSettingsStore();
+const { oidcAuthentication } = storeToRefs(configStore);
 const userAuthStore = useUserAuthStore();
 
 const { socket } = useWebsocketsStore();
@@ -41,7 +38,6 @@ const validation = reactive({
 const newPassword = ref();
 const oldPassword = ref();
 
-const { isPasswordLinked, isGithubLinked } = settingsStore;
 const { userId } = storeToRefs(userAuthStore);
 
 const togglePasswordVisibility = refName => {
@@ -83,20 +79,6 @@ const changePassword = () => {
 		}
 	);
 };
-const unlinkPassword = () => {
-	if (oidcAuthentication.value) return;
-
-	socket.dispatch("users.unlinkPassword", res => {
-		new Toast(res.message);
-	});
-};
-const unlinkGitHub = () => {
-	if (!githubAuthentication.value) return;
-
-	socket.dispatch("users.unlinkGitHub", res => {
-		new Toast(res.message);
-	});
-};
 const removeSessions = () => {
 	socket.dispatch(`users.removeSessions`, userId.value, res => {
 		new Toast(res.message);
@@ -122,153 +104,85 @@ watch(validation, newValidation => {
 
 <template>
 	<div class="content security-tab">
-		<div v-if="isPasswordLinked">
-			<h4 class="section-title">Change password</h4>
-
-			<p class="section-description">
-				You will need to know your previous password
-			</p>
-
-			<hr class="section-horizontal-rule" />
-
-			<p class="control is-expanded margin-top-zero">
-				<label for="old-password">Previous password</label>
-			</p>
-
-			<div id="password-visibility-container">
-				<input
-					class="input"
-					id="old-password"
-					ref="oldPassword"
-					type="password"
-					placeholder="Enter your old password here..."
-					v-model="validation.oldPassword.value"
-				/>
-				<a @click="togglePasswordVisibility('oldPassword')">
-					<i class="material-icons">
-						{{
-							!validation.oldPassword.visible
-								? "visibility"
-								: "visibility_off"
-						}}
-					</i>
-				</a>
-			</div>
-
-			<p class="control is-expanded">
-				<label for="new-password">New password</label>
-			</p>
-
-			<div id="password-visibility-container">
-				<input
-					class="input"
-					id="new-password"
-					type="password"
-					ref="newPassword"
-					placeholder="Enter new password here..."
-					v-model="validation.newPassword.value"
-					@keyup.enter="changePassword()"
-					@keypress="onInput('newPassword')"
-					@paste="onInput('newPassword')"
-				/>
-
-				<a @click="togglePasswordVisibility('newPassword')">
-					<i class="material-icons">
-						{{
-							!validation.newPassword.visible
-								? "visibility"
-								: "visibility_off"
-						}}
-					</i>
-				</a>
-			</div>
-
-			<transition name="fadein-helpbox">
-				<input-help-box
-					:entered="validation.newPassword.entered"
-					:valid="validation.newPassword.valid"
-					:message="validation.newPassword.message"
-				/>
-			</transition>
-
-			<p class="control">
-				<button
-					id="change-password-button"
-					class="button is-success"
-					@click.prevent="changePassword()"
-				>
-					Change password
-				</button>
-			</p>
-
-			<div class="section-margin-bottom" />
-		</div>
-
-		<div v-if="!isPasswordLinked && !oidcAuthentication">
-			<h4 class="section-title">Add a password</h4>
-			<p class="section-description">
-				Add a password, as an alternative to signing in with GitHub
-			</p>
-
-			<hr class="section-horizontal-rule" />
-
-			<router-link to="/set_password" class="button is-default"
-				><i class="material-icons icon-with-button">create</i>Set
-				Password
-			</router-link>
-
-			<div class="section-margin-bottom" />
+		<h4 class="section-title">Change password</h4>
+
+		<p class="section-description">
+			You will need to know your previous password
+		</p>
+
+		<hr class="section-horizontal-rule" />
+
+		<p class="control is-expanded margin-top-zero">
+			<label for="old-password">Previous password</label>
+		</p>
+
+		<div id="password-visibility-container">
+			<input
+				class="input"
+				id="old-password"
+				ref="oldPassword"
+				type="password"
+				placeholder="Enter your old password here..."
+				v-model="validation.oldPassword.value"
+			/>
+			<a @click="togglePasswordVisibility('oldPassword')">
+				<i class="material-icons">
+					{{
+						!validation.oldPassword.visible
+							? "visibility"
+							: "visibility_off"
+					}}
+				</i>
+			</a>
 		</div>
 
-		<div v-if="!isGithubLinked && githubAuthentication">
-			<h4 class="section-title">Link your GitHub account</h4>
-			<p class="section-description">
-				Link your {{ sitename }} account with GitHub
-			</p>
-
-			<hr class="section-horizontal-rule" />
-
-			<a
-				class="button is-github"
-				:href="`${configStore.urls.api}/auth/github/link`"
-			>
-				<div class="icon">
-					<img class="invert" src="/assets/social/github.svg" />
-				</div>
-				&nbsp; Link GitHub to account
+		<p class="control is-expanded">
+			<label for="new-password">New password</label>
+		</p>
+
+		<div id="password-visibility-container">
+			<input
+				class="input"
+				id="new-password"
+				type="password"
+				ref="newPassword"
+				placeholder="Enter new password here..."
+				v-model="validation.newPassword.value"
+				@keyup.enter="changePassword()"
+				@keypress="onInput('newPassword')"
+				@paste="onInput('newPassword')"
+			/>
+
+			<a @click="togglePasswordVisibility('newPassword')">
+				<i class="material-icons">
+					{{
+						!validation.newPassword.visible
+							? "visibility"
+							: "visibility_off"
+					}}
+				</i>
 			</a>
-
-			<div class="section-margin-bottom" />
 		</div>
 
-		<div v-if="isPasswordLinked && isGithubLinked">
-			<h4 class="section-title">Remove login methods</h4>
-			<p class="section-description">
-				Remove your password as a login method or unlink GitHub
-			</p>
-
-			<hr class="section-horizontal-rule" />
-
-			<div class="row">
-				<quick-confirm
-					v-if="isPasswordLinked && githubAuthentication"
-					@confirm="unlinkPassword()"
-				>
-					<a class="button is-danger">
-						<i class="material-icons icon-with-button">close</i>
-						Remove password
-					</a>
-				</quick-confirm>
-				<quick-confirm v-if="isGithubLinked" @confirm="unlinkGitHub()">
-					<a class="button is-danger">
-						<i class="material-icons icon-with-button">link_off</i>
-						Remove GitHub from account
-					</a>
-				</quick-confirm>
-			</div>
+		<transition name="fadein-helpbox">
+			<input-help-box
+				:entered="validation.newPassword.entered"
+				:valid="validation.newPassword.valid"
+				:message="validation.newPassword.message"
+			/>
+		</transition>
+
+		<p class="control">
+			<button
+				id="change-password-button"
+				class="button is-success"
+				@click.prevent="changePassword()"
+			>
+				Change password
+			</button>
+		</p>
 
-			<div class="section-margin-bottom" />
-		</div>
+		<div class="section-margin-bottom" />
 
 		<div>
 			<h4 class="section-title">Log out everywhere</h4>

+ 0 - 21
frontend/src/pages/Settings/index.vue

@@ -58,27 +58,6 @@ onMounted(() => {
 			value: true
 		})
 	);
-
-	socket.on("event:user.password.unlinked", () =>
-		updateOriginalUser({
-			property: "password",
-			value: false
-		})
-	);
-
-	socket.on("event:user.github.linked", () =>
-		updateOriginalUser({
-			property: "github",
-			value: true
-		})
-	);
-
-	socket.on("event:user.github.unlinked", () =>
-		updateOriginalUser({
-			property: "github",
-			value: false
-		})
-	);
 });
 </script>
 

+ 0 - 2
frontend/src/stores/config.ts

@@ -8,7 +8,6 @@ export const useConfigStore = defineStore("config", {
 			enabled: boolean;
 			key: string;
 		};
-		githubAuthentication: boolean;
 		oidcAuthentication: boolean;
 		messages: Record<string, string>;
 		christmas: boolean;
@@ -33,7 +32,6 @@ export const useConfigStore = defineStore("config", {
 			enabled: false,
 			key: ""
 		},
-		githubAuthentication: false,
 		oidcAuthentication: false,
 		messages: {
 			accountRemoval:

+ 0 - 5
frontend/src/stores/settings.ts

@@ -28,10 +28,5 @@ export const useSettingsStore = defineStore("settings", {
 			this.originalUser = user;
 			this.modifiedUser = JSON.parse(JSON.stringify(user));
 		}
-	},
-	getters: {
-		isGithubLinked: state => state.originalUser.github,
-		isOIDCLinked: state => state.originalUser.oidc,
-		isPasswordLinked: state => state.originalUser.password
 	}
 });

+ 0 - 5
frontend/src/types/user.ts

@@ -24,17 +24,12 @@ export interface User {
 				expires: Date;
 			};
 		};
-		github?: {
-			id: number;
-			access_token: string;
-		};
 		oidc?: {
 			sub: string;
 			access_token: string;
 		};
 	};
 	password?: boolean;
-	github?: boolean;
 	oidc?: boolean;
 	statistics: {
 		songsRequested: number;