app/src/services/AuthService.tstypescript
import router from "@/router";
import ax from "./RequestService";
import store from "@/store";
import i18n from "@/plugins/vue-i18n";
import { debug, error, warn } from "@/utils/logging";
import ERROR_CODES from "@/utils/error_codes";
import { AxiosResponse } from "axios";
import { User } from "@/types";
const tokenStorageKey = "choreo-planer-token";
/**
* Service for handling user authentication, including login, registration, and profile management.
*
* @class AuthService
*/
class AuthService {
/**
* @param {string} token - The authentication token to store
*/
saveLoginToken(token: string) {
localStorage.setItem(tokenStorageKey, token);
}
/**
* Make a login request with username and password and store the authentication token
*
* @async
* @param {string} username - The username of the user
* @param {string} password - The password of the user
* @returns {Boolean} Returns true if the login was successful, otherwise throws an error
*/
async login(username: string, password: string): Promise<boolean> {
debug("Attempting login", { username, password: "<redacted>" });
return ax
.post("/auth/login", { username, password })
.then((res) => {
const token = res.data as string;
if (!token) {
store.commit("setLoginState", false);
warn("Requested authentication token, but did not receive a token");
throw new Error("No token received");
}
debug("Successfully logged in as", username);
this.saveLoginToken(token);
store.commit("setLoginState", true);
store.dispatch("loadUserInfo");
return true;
})
.catch((e: Error) => {
error(e, ERROR_CODES.LOGIN_FAILED);
store.commit("setLoginState", false);
throw e;
});
}
/**
* Make a login request with an SSO token and store the authentication token
*
* @async
* @param {string} ssoToken - The SSO token received from the SSO provider
* @returns {Boolean} Returns true if the login was successful, otherwise throws an error
*/
async ssoLogin(ssoToken: string): Promise<boolean> {
debug("Attempting SSO login with token");
return ax
.post("/auth/sso", { ssoToken })
.then((res) => {
const token = res.data as string;
if (!token) {
store.commit("setLoginState", false);
warn(
"Requested authentication token with SSO token, but did not receive an authentication token"
);
throw new Error("No token received");
}
debug("Successfully logged in with SSO token");
this.saveLoginToken(token);
store.commit("setLoginState", true);
return true;
})
.catch((e: Error) => {
error(e, ERROR_CODES.SSO_LOGIN_FAILED);
store.commit("setLoginState", false);
throw e;
});
}
/**
* Request an SSO login link for the given email address.
*
* @async
* @param {string} email - The email address to request the SSO link for
* @returns {unknown} Returns the response data from the server
*/
async requestSSO(email: string): Promise<void> {
debug("Requesting SSO login link for mail address", email);
return ax.post("/auth/ssoRequest", { email }).then((res) => res.data);
}
/**
* Make a registration request with username, password, and email and store the authentication token
*
* @async
* @param {string} username - The username of the user
* @param {string} password - The password of the user
* @param {string} email - The email address of the user
* @returns {Boolean} Returns true if the registration was successful, otherwise throws an error
*/
async register(
username: string,
password: string,
email: string
): Promise<boolean> {
debug("Attempting to register", {
username,
email,
password: "<redacted>",
});
return ax
.post("/auth", { username, password, email })
.then((res) => {
const token = res.data as string;
if (!token) {
store.commit("setLoginState", false);
warn(
"Requested authentication token through registration token, but did not receive one"
);
throw new Error("No token received");
}
debug("Successfully registered as", username);
this.saveLoginToken(token);
store.commit("setLoginState", true);
store.dispatch("loadUserInfo");
return true;
})
.catch((e: Error) => {
error(e, ERROR_CODES.REGISTRATION_FAILED);
store.commit("setLoginState", false);
throw e;
});
}
/**
* Remove the authentication token and redirect to the login page if the current route is private
*
* @async
* @returns {void}
*/
async logout(): Promise<void> {
debug("Logging out.");
this.removeToken();
store.commit("setLoginState", false);
if (router.currentRoute.value.meta.private)
try {
router.push({
name: "Login",
params: { locale: i18n.global.locale.value },
});
} catch (e) {
error("Redundant navigation to login", ERROR_CODES.REDUNDANT_ROUTING);
}
debug("Successfully logged out");
}
/**
* Make a request to change the password of the currently logged-in user
*
* @param {string} password - The new password to set for the user
* @returns {unknown} Returns the response data from the server
*/
changePassword(password: string): Promise<void> {
debug("Changing password");
return ax.put("/user", { password }).then((res) => {
debug("Successfully changed password");
return res.data;
});
}
/**
* Make a request to update the user information of the currently logged-in user
*
* @param {string} username - The new username to set for the user
* @param {string} email - The new email address to set for the user
* @returns {unknown} Returns the response data from the server
*/
updateUserInfo(username: string, email: string): Promise<void> {
debug("Updating user info", { username, email });
return ax.put("/auth/me", { username, email }).then((res) => {
debug("Successfully updated user information");
return res.data;
});
}
/**
* Make a request to update the profile picture of the currently logged-in user
*
* @param {File} profilePicture - The new profile picture file to upload
* @returns {Promise<unknown>} Returns the response data from the server
*/
updateProfilePicture(profilePicture: File): Promise<void> {
debug("Updating profile picture");
const formData = new FormData();
formData.append("profilePicture", profilePicture);
return ax
.put("/auth/me/profilePicture", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
debug("Successfully updated profile picture");
return res.data;
});
}
/**
* Make a request to delete the currently logged-in user account
*
* @returns {Promise<unknown>} Returns the response data from the server
*/
deleteAccount(): Promise<void> {
debug("Deleting user account");
return ax.delete("/user").then((res) => {
this.logout();
debug("Successfully deleted user account");
return res.data;
});
}
/**
* Get the authentication token from local storage
*
* @returns {string}
*/
getAuthToken(): string | null {
return localStorage.getItem(tokenStorageKey);
}
/**
* Remove the authentication token from local storage
*
* @returns {void}
*/
removeToken(): void {
localStorage.removeItem(tokenStorageKey);
}
/**
* Get the user information of the currently logged-in user
*
* @returns {Promise<User>} Returns the user information from the server
*/
getUserInfo(skipRoutingOnFailure = false): Promise<User> {
return ax
.get("/auth/me", { skipRoutingToLogin: skipRoutingOnFailure } as Partial<
import("./RequestService").RequestConfig
>)
.then((res) => res.data);
}
/**
* Get the profile image of a user by user ID and image extension
*
* @param {string} userId - The ID of the user whose profile image is requested
* @param {string} extension - The file extension of the profile image (e.g., 'jpg', 'png')
* @returns {Promise<string>} Returns a promise that resolves to the profile image blob
*/
getProfileImage(
userId: string,
extension: string
): Promise<AxiosResponse<Blob>> {
const profileImageUrl = `/auth/me/profilePicture/${userId}.${extension}`;
debug("Querying profile image at", profileImageUrl);
return ax.get(profileImageUrl, { responseType: "blob" });
}
/**
* Make a request to delete the profile picture of the currently logged-in user
*
* @returns {Promise<unknown>} Returns the response data from the server
*/
deleteProfilePicture(): Promise<void> {
debug("Deleting profile picture");
return ax.delete("/auth/me/profilePicture").then((res) => {
debug("Successfully deleted profile picture");
return res.data;
});
}
/**
* Make a request to resend the email confirmation link for the currently logged-in user
*
* @returns {Promise<unknown>} Returns the response data from the server
*/
resendEmailConfirmationLink(): Promise<void> {
debug("Requesting the email confirmation email to be sent again");
return ax.get("/auth/me/resendEmailConfirmationLink").then((res) => {
debug("Successfully requesting resending of email confirmation email");
return res.data;
});
}
generateMcpToken(expiresIn: string = "30d"): Promise<{ token: string }> {
debug("Generating MCP token with expiration", expiresIn);
return ax.post("/auth/mcp-token", { expiresIn }).then((res) => {
debug("Successfully generated MCP token");
return res.data;
});
}
}
export default new AuthService();