Skip to content

Internal: Terms and Conditions improvements and fixes - refs BT#22774 #6468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/vue/components/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
v-if="allowRegistration"
v-t="'Sign up'"
class="btn btn--primary-outline"
href="/main/auth/inscription.php"
href="/main/auth/registration.php"
tabindex="3"
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion assets/vue/components/layout/TopbarNotLoggedIn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const menuItems = computed(() => {
if (allowRegistration.value) {
items.splice(2, 0, {
label: t("Registration"),
url: "/main/auth/inscription.php",
url: "/main/auth/registration.php",
})
}

Expand Down
177 changes: 97 additions & 80 deletions assets/vue/composables/auth/login.js
Original file line number Diff line number Diff line change
@@ -1,150 +1,167 @@
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useSecurityStore } from "../../store/securityStore";
import { usePlatformConfig } from "../../store/platformConfig";
import securityService from "../../services/securityService";
import { useNotification } from "../notification";
import { ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useSecurityStore } from "../../store/securityStore"
import { usePlatformConfig } from "../../store/platformConfig"
import securityService from "../../services/securityService"
import { useNotification } from "../notification"

function isValidHttpUrl(string) {
try {
const url = new URL(string);
return url.protocol === "http:" || url.protocol === "https:";
const url = new URL(string)
return url.protocol === "http:" || url.protocol === "https:"
} catch (_) {
return false;
return false
}
}

export function useLogin() {
const route = useRoute();
const router = useRouter();
const securityStore = useSecurityStore();
const platformConfigurationStore = usePlatformConfig();
const { showErrorNotification } = useNotification();
const route = useRoute()
const router = useRouter()
const securityStore = useSecurityStore()
const platformConfigurationStore = usePlatformConfig()
const { showErrorNotification } = useNotification()

const isLoading = ref(false);
const requires2FA = ref(false);
const isLoading = ref(false)
const requires2FA = ref(false)

async function performLogin(payload) {
isLoading.value = true;
requires2FA.value = false;
async function performLogin({ login, password, _remember_me, totp = null }) {
isLoading.value = true
requires2FA.value = false

try {
const responseData = await securityService.login(payload);
// Prepare payload as expected by securityService
const payload = {
login,
password,
_remember_me,
totp,
}

// Add returnUrl if exists in query param
const returnUrl = route.query.redirect?.toString() || null
if (returnUrl) {
payload.returnUrl = returnUrl
}

// Check if the backend demands 2FA and no TOTP was provided yet
const responseData = await securityService.login(payload)

// Handle 2FA flow
if (responseData.requires2FA && !payload.totp) {
requires2FA.value = true;
return { success: false, requires2FA: true };
requires2FA.value = true
return { success: false, requires2FA: true }
}

// Check rotate password flow
// Handle forced password rotation
if (responseData.rotate_password && responseData.redirect) {
window.location.href = responseData.redirect;
return { success: true, rotate: true };
window.location.href = responseData.redirect
return { success: true, rotate: true }
}

// Handle explicit backend error message
// Handle backend explicit error
if (responseData.error) {
showErrorNotification(responseData.error);
return { success: false, error: responseData.error };
showErrorNotification(responseData.error)
return { success: false, error: responseData.error }
}

// Special flow for terms acceptance
// Handle terms and conditions redirect
if (responseData.load_terms && responseData.redirect) {
window.location.href = responseData.redirect;
return { success: true, redirect: responseData.redirect };
window.location.href = responseData.redirect
return { success: true, redirect: responseData.redirect }
}

// Handle external redirect param
const redirectParam = route.query.redirect?.toString();
if (redirectParam) {
if (route.query.redirect) {
const redirectParam = route.query.redirect.toString()
if (isValidHttpUrl(redirectParam)) {
window.location.href = redirectParam;
window.location.href = redirectParam
} else {
await router.replace({ path: redirectParam });
await router.replace({ path: redirectParam })
}
return { success: true };
return { success: true }
}

// Fallback redirect from backend
if (responseData.redirect) {
window.location.href = responseData.redirect;
return { success: true };
window.location.href = responseData.redirect
return { success: true }
}

securityStore.setUser(responseData);
await platformConfigurationStore.initialize();
// Save user info
securityStore.setUser(responseData)
await platformConfigurationStore.initialize()

// Handle redirect param again after login
// Redirect again if redirect param still exists
if (route.query.redirect) {
await router.replace({ path: route.query.redirect.toString() });
return { success: true };
await router.replace({ path: route.query.redirect.toString() })
return { success: true }
}

// Determine post-login route from settings
const setting = platformConfigurationStore.getSetting("registration.redirect_after_login");
let target = "/";
// Default platform redirect after login
const setting = platformConfigurationStore.getSetting("registration.redirect_after_login")
let target = "/"

if (setting && typeof setting === "string") {
try {
const map = JSON.parse(setting);
const roles = responseData.roles || [];
const map = JSON.parse(setting)
const roles = responseData.roles || []

const getProfile = () => {
if (roles.includes("ROLE_ADMIN")) return "ADMIN";
if (roles.includes("ROLE_SESSION_MANAGER")) return "SESSIONADMIN";
if (roles.includes("ROLE_TEACHER")) return "COURSEMANAGER";
if (roles.includes("ROLE_STUDENT_BOSS")) return "STUDENT_BOSS";
if (roles.includes("ROLE_DRH")) return "DRH";
if (roles.includes("ROLE_INVITEE")) return "INVITEE";
if (roles.includes("ROLE_STUDENT")) return "STUDENT";
return null;
};

const profile = getProfile();
const value = profile && map[profile] ? map[profile] : "";
if (roles.includes("ROLE_ADMIN")) return "ADMIN"
if (roles.includes("ROLE_SESSION_MANAGER")) return "SESSIONADMIN"
if (roles.includes("ROLE_TEACHER")) return "COURSEMANAGER"
if (roles.includes("ROLE_STUDENT_BOSS")) return "STUDENT_BOSS"
if (roles.includes("ROLE_DRH")) return "DRH"
if (roles.includes("ROLE_INVITEE")) return "INVITEE"
if (roles.includes("ROLE_STUDENT")) return "STUDENT"
return null
}

const profile = getProfile()
const value = profile && map[profile] ? map[profile] : ""

switch (value) {
case "user_portal.php":
case "index.php":
target = "/home";
break;
target = "/home"
break
case "main/auth/courses.php":
target = "/courses";
break;
target = "/courses"
break
case "":
case null:
target = "/";
break;
target = "/"
break
default:
target = `/${value.replace(/^\/+/, "")}`;
target = `/${value.replace(/^\/+/, "")}`
}
} catch (e) {
console.warn("[redirect_after_login] Malformed JSON:", e);
console.warn("[redirect_after_login] Malformed JSON:", e)
}
}

await router.replace({ path: target });
return { success: true };
await router.replace({ path: target })

return { success: true }
} catch (error) {
const errorMessage =
error.response?.data?.error || "An error occurred during login.";
showErrorNotification(errorMessage);
return { success: false, error: errorMessage };
error.response?.data?.error || "An error occurred during login."
showErrorNotification(errorMessage)
return { success: false, error: errorMessage }
} finally {
isLoading.value = false;
isLoading.value = false
}
}

async function redirectNotAuthenticated() {
if (!securityStore.isAuthenticated) {
return;
return
}

const redirectParam = route.query.redirect?.toString();
const redirectParam = route.query.redirect?.toString()
if (redirectParam) {
await router.push({ path: redirectParam });
await router.push({ path: redirectParam })
} else {
await router.replace({ name: "Home" });
await router.replace({ name: "Home" })
}
}

Expand All @@ -153,5 +170,5 @@ export function useLogin() {
requires2FA,
performLogin,
redirectNotAuthenticated,
};
}
}
Loading
Loading