Browse Source

feat: Config options to disable GitHub authentication

Owen Diffey 1 year ago
parent
commit
4ee4dc6904

+ 1 - 0
.wiki/Configuration.md

@@ -24,6 +24,7 @@ Location: `backend/config/default.json`
 | `apis.youtube.quotas.limit` | YouTube API quota limit. |
 | `apis.recaptcha.secret` | ReCaptcha Site v3 secret, obtained from [here](https://www.google.com/recaptcha/admin). |
 | `apis.recaptcha.enabled` | Whether to enable ReCaptcha at email registration. |
+| `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 authorization callback url is the backend url with `/auth/github/authorize/callback` appended, for example `http://localhost/backend/auth/github/authorize/callback`. |

+ 2 - 1
backend/config/template.json

@@ -37,6 +37,7 @@
 			"enabled": false
 		},
 		"github": {
+			"enabled": false,
 			"client": "",
 			"secret": "",
 			"redirect_uri": ""
@@ -113,5 +114,5 @@
 			]
 		}
 	},
-	"configVersion": 10
+	"configVersion": 11
 }

+ 1 - 1
backend/index.js

@@ -6,7 +6,7 @@ import fs from "fs";
 
 import package_json from "./package.json" assert { type: "json" };
 
-const REQUIRED_CONFIG_VERSION = 10;
+const REQUIRED_CONFIG_VERSION = 11;
 
 // eslint-disable-next-line
 Array.prototype.remove = function (item) {

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

@@ -1184,7 +1184,8 @@ export default {
 		return async.waterfall(
 			[
 				next => {
-					userModel.findOne({ _id: session.userId }, (err, user) => next(err, user));
+					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) => {
@@ -2813,6 +2814,7 @@ export default {
 
 				(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);

+ 341 - 438
backend/logic/app.js

@@ -43,9 +43,7 @@ class _AppModule extends CoreClass {
 
 			const app = (this.app = express());
 			const SIDname = config.get("cookie.SIDname");
-			this.server = http
-				.createServer(app)
-				.listen(config.get("serverPort"));
+			this.server = http.createServer(app).listen(config.get("serverPort"));
 
 			app.use(cookieParser());
 
@@ -64,478 +62,399 @@ class _AppModule extends CoreClass {
 			app.use(cors(corsOptions));
 			app.options("*", cors(corsOptions));
 
-			const oauth2 = new OAuth2(
-				config.get("apis.github.client"),
-				config.get("apis.github.secret"),
-				"https://github.com/",
-				"login/oauth/authorize",
-				"login/oauth/access_token",
-				null
-			);
-
-			const redirectUri = `${config.get("apis.github.redirect_uri")}`;
-
 			/**
 			 * @param {object} res - response object from Express
 			 * @param {string} err - custom error message
 			 */
 			function redirectOnErr(res, err) {
-				res.redirect(
-					`${config.get("domain")}?err=${encodeURIComponent(err)}`
-				);
+				res.redirect(`${config.get("domain")}?err=${encodeURIComponent(err)}`);
 			}
 
-			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=${config.get("apis.github.redirect_uri")}`,
-					`scope=user:email`
-				].join("&");
-				return res.redirect(
-					`https://github.com/login/oauth/authorize?${params}`
+			if (config.get("apis.github.enabled")) {
+				const oauth2 = new OAuth2(
+					config.get("apis.github.client"),
+					config.get("apis.github.secret"),
+					"https://github.com/",
+					"login/oauth/authorize",
+					"login/oauth/access_token",
+					null
 				);
-			});
 
-			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 redirectUri = `${config.get("apis.github.redirect_uri")}`;
 
-				const params = [
-					`client_id=${config.get("apis.github.client")}`,
-					`redirect_uri=${config.get("apis.github.redirect_uri")}`,
-					`scope=user:email`,
-					`state=${req.cookies[SIDname]}`
-				].join("&");
-				return res.redirect(
-					`https://github.com/login/oauth/authorize?${params}`
-				);
-			});
+				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.");
+					}
 
-			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.`
-					);
+					const params = [
+						`client_id=${config.get("apis.github.client")}`,
+						`redirect_uri=${config.get("apis.github.redirect_uri")}`,
+						`scope=user:email`
+					].join("&");
+					return res.redirect(`https://github.com/login/oauth/authorize?${params}`);
+				});
 
-					return redirectOnErr(
-						res,
-						"Something went wrong on our end. Please try again later."
-					);
-				}
+				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 { code } = req.query;
-				let accessToken;
-				let body;
-				let address;
+					const params = [
+						`client_id=${config.get("apis.github.client")}`,
+						`redirect_uri=${config.get("apis.github.redirect_uri")}`,
+						`scope=user:email`,
+						`state=${req.cookies[SIDname]}`
+					].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.`
+						);
 
-				const { state } = req.query;
+						return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
+					}
 
-				const verificationToken = await UtilsModule.runJob(
-					"GENERATE_RANDOM_STRING",
-					{ length: 64 }
-				);
+					const { code } = req.query;
+					let accessToken;
+					let body;
+					let address;
 
-				return async.waterfall(
-					[
-						next => {
-							if (req.query.error)
-								return next(req.query.error_description);
-							return next();
-						},
+					const { state } = req.query;
 
-						next => {
-							oauth2.getOAuthAccessToken(
-								code,
-								{ redirect_uri: redirectUri },
-								next
-							);
-						},
+					const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 });
 
-						(_accessToken, refreshToken, results, next) => {
-							if (results.error)
-								return next(results.error_description);
+					return async.waterfall(
+						[
+							next => {
+								if (req.query.error) return next(req.query.error_description);
+								return next();
+							},
 
-							accessToken = _accessToken;
+							next => {
+								oauth2.getOAuthAccessToken(code, { redirect_uri: redirectUri }, next);
+							},
 
-							const options = {
-								headers: {
-									"User-Agent": "request",
-									Authorization: `token ${accessToken}`
-								}
-							};
+							(_accessToken, refreshToken, results, next) => {
+								if (results.error) return next(results.error_description);
 
-							return axios
-								.get("https://api.github.com/user", options)
-								.then(github => next(null, github))
-								.catch(err => next(err));
-						},
+								accessToken = _accessToken;
 
-						(github, next) => {
-							if (github.status !== 200)
-								return next(github.data.message);
-
-							if (state) {
-								return async.waterfall(
-									[
-										next => {
-											CacheModule.runJob("HGET", {
-												table: "sessions",
-												key: state
-											})
-												.then(session =>
-													next(null, session)
-												)
-												.catch(next);
-										},
-
-										(session, next) => {
-											if (!session)
-												return next("Invalid session.");
-											return userModel.findOne(
-												{ _id: session.userId },
-												next
-											);
-										},
-
-										(user, next) => {
-											if (!user)
-												return next("User not found.");
-											if (
-												user.services.github &&
-												user.services.github.id
-											)
-												return next(
-													"Account already has GitHub linked."
-												);
-
-											return userModel.updateOne(
-												{ _id: user._id },
-												{
-													$set: {
-														"services.github": {
-															id: github.data.id,
-															access_token:
-																accessToken
+								const options = {
+									headers: {
+										"User-Agent": "request",
+										Authorization: `token ${accessToken}`
+									}
+								};
+
+								return axios
+									.get("https://api.github.com/user", options)
+									.then(github => next(null, github))
+									.catch(err => next(err));
+							},
+
+							(github, next) => {
+								if (github.status !== 200) return next(github.data.message);
+
+								if (state) {
+									return async.waterfall(
+										[
+											next => {
+												CacheModule.runJob("HGET", {
+													table: "sessions",
+													key: state
+												})
+													.then(session => next(null, session))
+													.catch(next);
+											},
+
+											(session, next) => {
+												if (!session) return next("Invalid session.");
+												return userModel.findOne({ _id: session.userId }, next);
+											},
+
+											(user, next) => {
+												if (!user) return next("User not found.");
+												if (user.services.github && user.services.github.id)
+													return next("Account already has GitHub linked.");
+
+												return userModel.updateOne(
+													{ _id: user._id },
+													{
+														$set: {
+															"services.github": {
+																id: github.data.id,
+																access_token: accessToken
+															}
 														}
+													},
+													{ runValidators: true },
+													err => {
+														if (err) return next(err);
+														return next(null, user, github.data);
 													}
-												},
-												{ runValidators: true },
-												err => {
-													if (err) return next(err);
-													return next(
-														null,
-														user,
-														github.data
-													);
-												}
-											);
-										},
-
-										user => {
-											CacheModule.runJob("PUB", {
-												channel: "user.linkGithub",
-												value: user._id
-											});
-
-											CacheModule.runJob("PUB", {
-												channel: "user.updated",
-												value: { userId: user._id }
-											});
-
-											res.redirect(
-												`${config.get(
-													"domain"
-												)}/settings?tab=security`
-											);
-										}
-									],
-									next
-								);
-							}
+												);
+											},
+
+											user => {
+												CacheModule.runJob("PUB", {
+													channel: "user.linkGithub",
+													value: user._id
+												});
+
+												CacheModule.runJob("PUB", {
+													channel: "user.updated",
+													value: { userId: user._id }
+												});
+
+												res.redirect(`${config.get("domain")}/settings?tab=security`);
+											}
+										],
+										next
+									);
+								}
 
-							if (!github.data.id)
-								return next("Something went wrong, no id.");
+								if (!github.data.id) return next("Something went wrong, no id.");
 
-							return userModel.findOne(
-								{ "services.github.id": github.data.id },
-								(err, user) => {
+								return userModel.findOne({ "services.github.id": github.data.id }, (err, user) => {
 									next(err, user, github.data);
-								}
-							);
-						},
-
-						(user, _body, next) => {
-							body = _body;
+								});
+							},
 
-							if (user) {
-								user.services.github.access_token = accessToken;
-								return user.save(() => next(true, user._id));
-							}
+							(user, _body, next) => {
+								body = _body;
 
-							return userModel.findOne(
-								{
-									username: new RegExp(`^${body.login}$`, "i")
-								},
-								(err, user) => next(err, user)
-							);
-						},
+								if (user) {
+									user.services.github.access_token = accessToken;
+									return user.save(() => next(true, user._id));
+								}
 
-						(user, next) => {
-							if (user)
-								return next(
-									`An account with that username already exists.`
+								return userModel.findOne(
+									{
+										username: new RegExp(`^${body.login}$`, "i")
+									},
+									(err, user) => next(err, user)
 								);
+							},
 
-							return axios
-								.get("https://api.github.com/user/emails", {
-									headers: {
-										"User-Agent": "request",
-										Authorization: `token ${accessToken}`
-									}
-								})
-								.then(res => next(null, res.data))
-								.catch(err => next(err));
-						},
+							(user, next) => {
+								if (user) return next(`An account with that username already exists.`);
 
-						(body, next) => {
-							if (!Array.isArray(body)) return next(body.message);
-
-							body.forEach(email => {
-								if (email.primary)
-									address = email.email.toLowerCase();
-							});
+								return axios
+									.get("https://api.github.com/user/emails", {
+										headers: {
+											"User-Agent": "request",
+											Authorization: `token ${accessToken}`
+										}
+									})
+									.then(res => next(null, res.data))
+									.catch(err => next(err));
+							},
 
-							return userModel.findOne(
-								{ "email.address": address },
-								next
-							);
-						},
+							(body, next) => {
+								if (!Array.isArray(body)) return next(body.message);
 
-						(user, next) => {
-							UtilsModule.runJob("GENERATE_RANDOM_STRING", {
-								length: 12
-							}).then(_id => next(null, user, _id));
-						},
-
-						(user, _id, next) => {
-							if (user) {
-								if (
-									Object.keys(
-										JSON.parse(user.services.github)
-									).length === 0
-								)
-									return next(
-										`An account with that email address exists, but is not linked to GitHub.`
-									);
-								return next(
-									`An account with that email address already exists.`
-								);
-							}
+								body.forEach(email => {
+									if (email.primary) address = email.email.toLowerCase();
+								});
 
-							return next(null, {
-								_id,
-								username: body.login,
-								name: body.name,
-								location: body.location,
-								bio: body.bio,
-								email: {
-									address,
-									verificationToken
-								},
-								services: {
-									github: {
-										id: body.id,
-										access_token: accessToken
-									}
+								return userModel.findOne({ "email.address": address }, next);
+							},
+
+							(user, next) => {
+								UtilsModule.runJob("GENERATE_RANDOM_STRING", {
+									length: 12
+								}).then(_id => next(null, user, _id));
+							},
+
+							(user, _id, next) => {
+								if (user) {
+									if (Object.keys(JSON.parse(user.services.github)).length === 0)
+										return next(
+											`An account with that email address exists, but is not linked to GitHub.`
+										);
+									return next(`An account with that email address already exists.`);
 								}
-							});
-						},
-
-						// generate the url for gravatar avatar
-						(user, next) => {
-							UtilsModule.runJob("CREATE_GRAVATAR", {
-								email: user.email.address
-							}).then(url => {
-								user.avatar = { type: "gravatar", url };
-								next(null, user);
-							});
-						},
-
-						// save the new user to the database
-						(user, next) => {
-							userModel.create(user, next);
-						},
 
-						(user, next) => {
-							MailModule.runJob("GET_SCHEMA", {
-								schemaName: "verifyEmail"
-							}).then(verifyEmailSchema => {
-								verifyEmailSchema(
-									address,
-									body.login,
-									user.email.verificationToken,
-									err => {
-										next(err, user._id);
+								return next(null, {
+									_id,
+									username: body.login,
+									name: body.name,
+									location: body.location,
+									bio: body.bio,
+									email: {
+										address,
+										verificationToken
+									},
+									services: {
+										github: {
+											id: body.id,
+											access_token: accessToken
+										}
 									}
-								);
-							});
-						},
-
-						// create a liked songs playlist for the new user
-						(userId, next) => {
-							PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
-								userId,
-								displayName: "Liked Songs",
-								type: "user-liked"
-							})
-								.then(likedSongsPlaylist => {
-									next(null, likedSongsPlaylist, userId);
+								});
+							},
+
+							// generate the url for gravatar avatar
+							(user, next) => {
+								UtilsModule.runJob("CREATE_GRAVATAR", {
+									email: user.email.address
+								}).then(url => {
+									user.avatar = { type: "gravatar", url };
+									next(null, user);
+								});
+							},
+
+							// save the new user to the database
+							(user, next) => {
+								userModel.create(user, next);
+							},
+
+							(user, next) => {
+								MailModule.runJob("GET_SCHEMA", {
+									schemaName: "verifyEmail"
+								}).then(verifyEmailSchema => {
+									verifyEmailSchema(address, body.login, user.email.verificationToken, err => {
+										next(err, user._id);
+									});
+								});
+							},
+
+							// create a liked songs playlist for the new user
+							(userId, next) => {
+								PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
+									userId,
+									displayName: "Liked Songs",
+									type: "user-liked"
 								})
-								.catch(err => next(err));
-						},
-
-						// create a disliked songs playlist for the new user
-						(likedSongsPlaylist, userId, next) => {
-							PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
-								userId,
-								displayName: "Disliked Songs",
-								type: "user-disliked"
-							})
-								.then(dislikedSongsPlaylist => {
-									next(
-										null,
-										{
+									.then(likedSongsPlaylist => {
+										next(null, likedSongsPlaylist, userId);
+									})
+									.catch(err => next(err));
+							},
+
+							// create a disliked songs playlist for the new user
+							(likedSongsPlaylist, userId, next) => {
+								PlaylistsModule.runJob("CREATE_USER_PLAYLIST", {
+									userId,
+									displayName: "Disliked Songs",
+									type: "user-disliked"
+								})
+									.then(dislikedSongsPlaylist => {
+										next(
+											null,
+											{
+												likedSongsPlaylist,
+												dislikedSongsPlaylist
+											},
+											userId
+										);
+									})
+									.catch(err => next(err));
+							},
+
+							// associate liked + disliked songs playlist to the user object
+							({ likedSongsPlaylist, dislikedSongsPlaylist }, userId, next) => {
+								userModel.updateOne(
+									{ _id: userId },
+									{
+										$set: {
 											likedSongsPlaylist,
 											dislikedSongsPlaylist
-										},
-										userId
-									);
-								})
-								.catch(err => next(err));
-						},
-
-						// associate liked + disliked songs playlist to the user object
-						(
-							{ likedSongsPlaylist, dislikedSongsPlaylist },
-							userId,
-							next
-						) => {
-							userModel.updateOne(
-								{ _id: userId },
-								{
-									$set: {
-										likedSongsPlaylist,
-										dislikedSongsPlaylist
+										}
+									},
+									{ runValidators: true },
+									err => {
+										if (err) return next(err);
+										return next(null, userId);
 									}
-								},
-								{ runValidators: true },
-								err => {
-									if (err) return next(err);
-									return next(null, userId);
-								}
-							);
-						},
-
-						// add the activity of account creation
-						(userId, next) => {
-							ActivitiesModule.runJob("ADD_ACTIVITY", {
-								userId,
-								type: "user__joined",
-								payload: { message: "Welcome to Musare!" }
-							});
-
-							next(null, userId);
-						}
-					],
-					async (err, userId) => {
-						if (err && err !== true) {
-							err = await UtilsModule.runJob("GET_ERROR", {
-								error: err
-							});
-
-							this.log(
-								"ERROR",
-								"AUTH_GITHUB_AUTHORIZE_CALLBACK",
-								`Failed to authorize with GitHub. "${err}"`
-							);
-
-							return redirectOnErr(res, err);
-						}
-
-						const sessionId = await UtilsModule.runJob("GUID", {});
-						const sessionSchema = await CacheModule.runJob(
-							"GET_SCHEMA",
-							{
-								schemaName: "session"
-							}
-						);
-
-						return CacheModule.runJob("HSET", {
-							table: "sessions",
-							key: sessionId,
-							value: sessionSchema(sessionId, userId)
-						})
-							.then(() => {
-								const date = new Date();
-								date.setTime(
-									new Date().getTime() +
-										2 * 365 * 24 * 60 * 60 * 1000
 								);
+							},
+
+							// add the activity of account creation
+							(userId, next) => {
+								ActivitiesModule.runJob("ADD_ACTIVITY", {
+									userId,
+									type: "user__joined",
+									payload: { message: "Welcome to Musare!" }
+								});
 
-								res.cookie(SIDname, sessionId, {
-									expires: date,
-									secure: config.get("cookie.secure"),
-									path: "/",
-									domain: config.get("cookie.domain")
+								next(null, userId);
+							}
+						],
+						async (err, userId) => {
+							if (err && err !== true) {
+								err = await UtilsModule.runJob("GET_ERROR", {
+									error: err
 								});
 
 								this.log(
-									"INFO",
+									"ERROR",
 									"AUTH_GITHUB_AUTHORIZE_CALLBACK",
-									`User "${userId}" successfully authorized with GitHub.`
+									`Failed to authorize with GitHub. "${err}"`
 								);
 
-								res.redirect(`${config.get("domain")}/`);
+								return redirectOnErr(res, err);
+							}
+
+							const sessionId = await UtilsModule.runJob("GUID", {});
+							const sessionSchema = await CacheModule.runJob("GET_SCHEMA", {
+								schemaName: "session"
+							});
+
+							return CacheModule.runJob("HSET", {
+								table: "sessions",
+								key: sessionId,
+								value: sessionSchema(sessionId, userId)
 							})
-							.catch(err => redirectOnErr(res, err.message));
-					}
-				);
-			});
+								.then(() => {
+									const date = new Date();
+									date.setTime(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
+
+									res.cookie(SIDname, sessionId, {
+										expires: date,
+										secure: config.get("cookie.secure"),
+										path: "/",
+										domain: config.get("cookie.domain")
+									});
+
+									this.log(
+										"INFO",
+										"AUTH_GITHUB_AUTHORIZE_CALLBACK",
+										`User "${userId}" successfully authorized with GitHub.`
+									);
+
+									res.redirect(`${config.get("domain")}/`);
+								})
+								.catch(err => redirectOnErr(res, err.message));
+						}
+					);
+				});
+			}
 
 			app.get("/auth/verify_email", 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."
+						"APP_REJECTED_VERIFY_EMAIL",
+						`A user tried to use verify email, but the APP module is currently not ready.`
 					);
+					return redirectOnErr(res, "Something went wrong on our end. Please try again later.");
 				}
 
 				const { code } = req.query;
@@ -548,16 +467,12 @@ class _AppModule extends CoreClass {
 						},
 
 						next => {
-							userModel.findOne(
-								{ "email.verificationToken": code },
-								next
-							);
+							userModel.findOne({ "email.verificationToken": code }, next);
 						},
 
 						(user, next) => {
 							if (!user) return next("User not found.");
-							if (user.email.verified)
-								return next("This email is already verified.");
+							if (user.email.verified) return next("This email is already verified.");
 
 							return userModel.updateOne(
 								{ "email.verificationToken": code },
@@ -577,11 +492,7 @@ class _AppModule extends CoreClass {
 							if (typeof err === "string") error = err;
 							else if (err.message) error = err.message;
 
-							this.log(
-								"ERROR",
-								"VERIFY_EMAIL",
-								`Verifying email failed. "${error}"`
-							);
+							this.log("ERROR", "VERIFY_EMAIL", `Verifying email failed. "${error}"`);
 
 							return res.json({
 								status: "error",
@@ -589,17 +500,9 @@ class _AppModule extends CoreClass {
 							});
 						}
 
-						this.log(
-							"INFO",
-							"VERIFY_EMAIL",
-							`Successfully verified email.`
-						);
+						this.log("INFO", "VERIFY_EMAIL", `Successfully verified email.`);
 
-						return res.redirect(
-							`${config.get(
-								"domain"
-							)}?msg=Thank you for verifying your email`
-						);
+						return res.redirect(`${config.get("domain")}?msg=Thank you for verifying your email`);
 					}
 				);
 			});

+ 3 - 2
frontend/dist/config/template.json

@@ -28,7 +28,8 @@
 		},
 		"mediasession": false,
 		"christmas": false,
-		"registrationDisabled": false
+		"registrationDisabled": false,
+		"githubAuthentication": false
 	},
 	"messages": {
 		"accountRemoval": "Your account will be deactivated instantly and your data will shortly be deleted by an admin."
@@ -53,5 +54,5 @@
 		"version": true
 	},
 	"skipConfigVersionCheck": false,
-	"configVersion": 12
+	"configVersion": 13
 }

+ 6 - 4
frontend/src/App.vue

@@ -267,10 +267,12 @@ onMounted(async () => {
 			new Toast({ content: msg, timeout: 20000 });
 		}
 
-		if (localStorage.getItem("github_redirect")) {
-			router.push(localStorage.getItem("github_redirect"));
-			localStorage.removeItem("github_redirect");
-		}
+		lofig.get("siteSettings.githubAuthentication").then(enabled => {
+			if (enabled && localStorage.getItem("github_redirect")) {
+				router.push(localStorage.getItem("github_redirect"));
+				localStorage.removeItem("github_redirect");
+			}
+		});
 	});
 
 	if (localStorage.getItem("nightmode") === "true") {

+ 7 - 5
frontend/src/components/modals/Login.vue

@@ -12,7 +12,10 @@ const password = ref({
 	visible: false
 });
 const apiDomain = ref("");
-const registrationDisabled = ref(false);
+const siteSettings = ref({
+	registrationDisabled: false,
+	githubAuthentication: false
+});
 const passwordElement = ref();
 
 const store = useStore();
@@ -66,9 +69,7 @@ const githubRedirect = () => {
 
 onMounted(async () => {
 	apiDomain.value = await lofig.get("backend.apiDomain");
-	registrationDisabled.value = await lofig.get(
-		"siteSettings.registrationDisabled"
-	);
+	siteSettings.value = await lofig.get("siteSettings");
 });
 </script>
 
@@ -149,6 +150,7 @@ onMounted(async () => {
 						Login
 					</button>
 					<a
+						v-if="siteSettings.githubAuthentication"
 						class="button is-github"
 						:href="apiDomain + '/auth/github/authorize'"
 						@click="githubRedirect()"
@@ -164,7 +166,7 @@ onMounted(async () => {
 				</div>
 
 				<p
-					v-if="!registrationDisabled"
+					v-if="!siteSettings.registrationDisabled"
 					class="content-box-optional-helper"
 				>
 					<a @click="changeToRegisterModal()">

+ 8 - 4
frontend/src/components/modals/Register.vue

@@ -37,7 +37,10 @@ const recaptcha = ref({
 	enabled: false
 });
 const apiDomain = ref("");
-const registrationDisabled = ref(false);
+const siteSettings = ref({
+	registrationDisabled: false,
+	githubAuthentication: false
+});
 const passwordElement = ref();
 
 const store = useStore();
@@ -146,12 +149,12 @@ watch(
 
 onMounted(async () => {
 	apiDomain.value = await lofig.get("backend.apiDomain");
-	lofig.get("siteSettings.registrationDisabled").then(res => {
-		if (res) {
+	lofig.get("siteSettings").then(settings => {
+		if (settings.registrationDisabled) {
 			new Toast("Registration is disabled.");
 			closeCurrentModal();
 		} else {
-			registrationDisabled.value = res;
+			siteSettings.value = settings;
 		}
 	});
 
@@ -281,6 +284,7 @@ onMounted(async () => {
 						Register
 					</button>
 					<a
+						v-if="siteSettings.githubAuthentication"
 						class="button is-github"
 						:href="apiDomain + '/auth/github/authorize'"
 						@click="githubRedirect()"

+ 17 - 3
frontend/src/components/modals/RemoveAccount.vue

@@ -35,6 +35,7 @@ const password = ref({
 	visible: false
 });
 const passwordElement = ref();
+const githubAuthentication = ref(false);
 
 const checkForAutofill = (cb, event) => {
 	if (
@@ -107,6 +108,9 @@ const remove = () =>
 onMounted(async () => {
 	apiDomain.value = await lofig.get("backend.apiDomain");
 	accountRemovalMessage.value = await lofig.get("messages.accountRemoval");
+	githubAuthentication.value = await lofig.get(
+		"siteSettings.githubAuthentication"
+	);
 
 	if (githubLinkConfirmed.value === true) confirmGithubLink();
 });
@@ -160,7 +164,10 @@ onMounted(async () => {
 			<div
 				class="content-box"
 				id="password-linked"
-				v-if="step === 'confirm-identity' && isPasswordLinked"
+				v-if="
+					step === 'confirm-identity' &&
+					(isPasswordLinked || !githubAuthentication)
+				"
 			>
 				<h2 class="content-box-title">Enter your password</h2>
 				<p class="content-box-description">
@@ -217,7 +224,11 @@ onMounted(async () => {
 
 			<div
 				class="content-box"
-				v-else-if="isGithubLinked && step === 'confirm-identity'"
+				v-else-if="
+					githubAuthentication &&
+					isGithubLinked &&
+					step === 'confirm-identity'
+				"
 			>
 				<h2 class="content-box-title">Verify your GitHub</h2>
 				<p class="content-box-description">
@@ -237,7 +248,10 @@ onMounted(async () => {
 				</div>
 			</div>
 
-			<div class="content-box" v-if="step === 'relink-github'">
+			<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

+ 1 - 1
frontend/src/main.ts

@@ -16,7 +16,7 @@ const defaultConfigURL = new URL(
 	import.meta.url
 ).toString();
 
-const REQUIRED_CONFIG_VERSION = 12;
+const REQUIRED_CONFIG_VERSION = 13;
 
 lofig.folder = defaultConfigURL;
 

+ 8 - 5
frontend/src/pages/Settings/Tabs/Security.vue

@@ -22,7 +22,10 @@ const store = useStore();
 const { socket } = store.state.websockets;
 
 const apiDomain = ref("");
-const sitename = ref("Musare");
+const siteSettings = ref({
+	sitename: "Musare",
+	githubAuthentication: false
+});
 const validation = reactive({
 	oldPassword: {
 		value: "",
@@ -107,7 +110,7 @@ const removeSessions = () => {
 
 onMounted(async () => {
 	apiDomain.value = await lofig.get("backend.apiDomain");
-	sitename.value = await lofig.get("siteSettings.sitename");
+	siteSettings.value = await lofig.get("siteSettings");
 });
 
 watch(validation, newValidation => {
@@ -227,10 +230,10 @@ watch(validation, newValidation => {
 			<div class="section-margin-bottom" />
 		</div>
 
-		<div v-if="!isGithubLinked">
+		<div v-if="!isGithubLinked && siteSettings.githubAuthentication">
 			<h4 class="section-title">Link your GitHub account</h4>
 			<p class="section-description">
-				Link your {{ sitename }} account with GitHub
+				Link your {{ siteSettings.sitename }} account with GitHub
 			</p>
 
 			<hr class="section-horizontal-rule" />
@@ -255,7 +258,7 @@ watch(validation, newValidation => {
 
 			<div class="row">
 				<quick-confirm
-					v-if="isPasswordLinked"
+					v-if="isPasswordLinked && siteSettings.githubAuthentication"
 					@confirm="unlinkPassword()"
 				>
 					<a class="button is-danger">