services_AuthService.js

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";

const tokenStorageKey = "choreo-planer-token";

/**
 * Service for handling user authentication, including login, registration, and profile management.
 *
 * @class AuthService
 */
class AuthService {
  /**
   * 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, password) {
    debug("Attempting login", { username, password: "<redacted>" });
    return ax
      .post("/auth/login", { username, password })
      .then((res) => {
        const token = res.data;
        if (!token) {
          store.commit("setLoginState", false);
          warn("Requested authentication token, but did not receive a token");
          throw new Error("No token received");
        }

        debug("Succesfully logged in as", username);
        localStorage.setItem(tokenStorageKey, token);
        store.commit("setLoginState", true);
        return true;
      })
      .catch((e) => {
        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) {
    debug("Attempting SSO login with token");
    return ax
      .post("/auth/sso", { ssoToken })
      .then((res) => {
        const token = res.data;
        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");
        localStorage.setItem(tokenStorageKey, token);
        store.commit("setLoginState", true);
        return true;
      })
      .catch((e) => {
        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) {
    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, password, email) {
    debug("Attempting to register", {
      username,
      email,
      password: "<redacted>",
    });
    return ax
      .post("/auth", { username, password, email })
      .then((res) => {
        const token = res.data;
        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);
        localStorage.setItem(tokenStorageKey, token);
        store.commit("setLoginState", true);
        return true;
      })
      .catch((e) => {
        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() {
    debug("Logging out.");
    this.removeToken();
    store.commit("setLoginState", false);
    if (router.currentRoute.meta.private)
      router
        .push({ name: "Login", params: { locale: i18n.locale } })
        .catch(() => {
          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) {
    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, email) {
    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) {
    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() {
    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() {
    return localStorage.getItem(tokenStorageKey);
  }

  /**
   * Remove the authentication token from local storage
   *
   * @returns {void}
   */
  removeToken() {
    return localStorage.removeItem(tokenStorageKey);
  }

  /**
   * Get the user information of the currently logged-in user
   *
   * @returns {Promise<object>} Returns the user information from the server
   */
  getUserInfo() {
    return ax.get("/auth/me").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, extension) {
    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() {
    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() {
    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;
    });
  }
}

export default new AuthService();