Skip to content
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ Complete the `appsettings.json` file with the following content, replacing place
"ClientSecret": "<client-secret>",
"Authority": "https://<sso-server-url>/application/o/authorize/",
"RedirectUri": "https://<frontend-url>/auth/callback",
"Scope": "openid profile email"
"Scope": "openid profile email",
"DisplayName": "<method-name>",
"IconUrl": "<method-icon-url>"
}
},
"S3": {
Expand Down
4 changes: 3 additions & 1 deletion docs/01_installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ Complete the `appsettings.json` file with the following content, replacing place
"ClientSecret": "<client-secret>",
"Authority": "https://<sso-server-url>/application/o/authorize/",
"RedirectUri": "https://<frontend-url>/auth/callback",
"Scope": "openid profile email"
"Scope": "openid profile email",
"DisplayName": "<method-name>",
"IconUrl": "<method-icon-url>"
}
},
"S3": {
Expand Down
8 changes: 8 additions & 0 deletions electrostoreAPI/DTO/configDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ public record ReadConfig
public int max_length_reason { get; init; }
public int max_length_status { get; init; }
public int max_size_document_in_mb { get; init; }
public List<SSOAvailableProvider>? sso_available_providers { get; init; }
}

public record SSOAvailableProvider
{
public required string provider { get; init; }
public required string display_name { get; init; }
public required string icon_url { get; init; }
}

public static class Constants
{
public const int MaxUrlLength = 150;
Expand Down
8 changes: 7 additions & 1 deletion electrostoreAPI/Services/ConfigService/ConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ public async Task<ReadConfig> getAllConfig()
max_length_ip = Constants.MaxIpLength,
max_length_reason = Constants.MaxReasonLength,
max_length_status = Constants.MaxStatusLength,
max_size_document_in_mb = Constants.MaxDocumentSizeMB
max_size_document_in_mb = Constants.MaxDocumentSizeMB,
sso_available_providers = _configuration.GetSection("OAuth").GetChildren().Select(provider => new SSOAvailableProvider
{
provider = provider.Key,
display_name = provider.GetValue<string>("DisplayName") ?? string.Empty,
icon_url = provider.GetValue<string>("IconUrl") ?? string.Empty
}).ToList()
};
}
}
4 changes: 3 additions & 1 deletion electrostoreAPI/config/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"ClientSecret": "<client-secret>",
"Authority": "https://<sso-server-url>/application/o/authorize/",
"RedirectUri": "https://<frontend-url>/auth/callback",
"Scope": "openid profile email"
"Scope": "openid profile email",
"DisplayName": "<method-name>",
"IconUrl": "<method-icon-url>"
}
},
"S3": {
Expand Down
4 changes: 3 additions & 1 deletion electrostoreAPI/config/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"ClientSecret": "<client-secret>",
"Authority": "https://<sso-server-url>/application/o/authorize/",
"RedirectUri": "https://<frontend-url>/auth/callback",
"Scope": "openid profile email"
"Scope": "openid profile email",
"DisplayName": "<method-name>",
"IconUrl": "<method-icon-url>"
}
},
"S3": {
Expand Down
4 changes: 3 additions & 1 deletion electrostoreFRONT/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"VLoginTitle": "Login",
"VLoginEmail": "Email",
"VLoginSubmit": "Login",
"VLoginSSOSubmit": "Login with SSO",
"VLoginLoadingSSO": "Loading SSO providers...",
"VLoginSSOLoginWith": "Login with {provider}",
"VLoginNoSSOProviders": "No SSO providers available",
"VLoginPassword": "Password",
"VLoginEmailRequired": "Email is required",
"VLoginPasswordRequired": "Password is required",
Expand Down
4 changes: 3 additions & 1 deletion electrostoreFRONT/src/locales/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"VLoginTitle": "Connexion",
"VLoginEmail": "Adresse e-mail",
"VLoginSubmit": "Se connecter",
"VLoginSSOSubmit": "Se connecter en SSO",
"VLoginLoadingSSO": "Chargement des fournisseurs SSO...",
"VLoginSSOLoginWith": "Se connecter avec {provider}",
"VLoginNoSSOProviders": "Aucun fournisseur SSO disponible",
"VLoginPassword": "Mot de passe",
"VLoginEmailRequired": "L'adresse e-mail est obligatoire",
"VLoginPasswordRequired": "Le mot de passe est obligatoire",
Expand Down
20 changes: 11 additions & 9 deletions electrostoreFRONT/src/stores/auth.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const useAuthStore = defineStore("auth",{
accessToken: JSON.parse(localStorage.getItem("accessToken")) || null,
refreshToken: JSON.parse(localStorage.getItem("refreshToken")) || null,
isSSOUser: JSON.parse(localStorage.getItem("isSSOUser")) || false,
selectedProvider: JSON.parse(localStorage.getItem("selectedProvider")) || null,
}),
actions: {
setToken(user, accessToken, refreshToken, isSSOUser = false) {
Expand Down Expand Up @@ -62,22 +63,22 @@ export const useAuthStore = defineStore("auth",{
);
router.push("/");
},
async loginSSO() {
async loginSSO(provider) {
const request = await fetchWrapper.get({
url: `${baseUrl}/auth/authentik/url`,
url: `${baseUrl}/auth/${provider}/url`,
});
this.selectedProvider = provider;
localStorage.setItem("selectedProvider", JSON.stringify(provider));
// open small window to the url
//window.open(request.url, "SSO Login", "width=600,height=600");
// redirect to the url
window.location.href = request.authUrl;
window.open(request.authUrl, "SSO Login", "width=600,height=600");
},
async handleSSOCallback() {
const params = new URLSearchParams(window.location.search);
const token = params.get("code");
const state = params.get("state");
if (token && state) {
const request = await fetchWrapper.post({
url: `${baseUrl}/auth/authentik/callback`,
url: `${baseUrl}/auth/${this.selectedProvider}/callback`,
body: { "Code": token, "State": state },
});
this.setToken(
Expand All @@ -86,9 +87,10 @@ export const useAuthStore = defineStore("auth",{
{ "token": request?.refresh_token, "date_expire": request?.expire_date_refresh_token },
true,
);
localStorage.removeItem("ssoState");
// redirect to previous url or default to home page if no previous url or if previous url is login page
router.push((this.returnUrl && this.returnUrl !== "/login") ? this.returnUrl : "/");
localStorage.removeItem("selectedProvider");
// close small window and refresh parent window
window.opener.location.reload();
window.close();
}
},
async register(email, password, prenom, nom) {
Expand Down
1 change: 1 addition & 0 deletions electrostoreFRONT/src/stores/config.store.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const useConfigsStore = defineStore("configs",{
"max_length_reason": 50,
"max_length_status": 50,
"max_size_document_in_mb": 5,
"sso_available_providers": [],// e.g : [{"provider":"authentik","display_name":"Authentik","icon_url":"https://example.com/icon.png"}]
},
}),
actions: {
Expand Down
32 changes: 24 additions & 8 deletions electrostoreFRONT/src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import * as Yup from "yup";
import { useI18n } from "vue-i18n";
const { t } = useI18n();

import { useAuthStore } from "@/stores";
import { useAuthStore, useConfigsStore } from "@/stores";
const configsStore = useConfigsStore();
const authStore = useAuthStore();

const showPassword = ref(false);
Expand Down Expand Up @@ -66,18 +67,33 @@ function onSubmit(values, { setErrors }) {
class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2 inline-block"></span>
{{ $t('common.VLoginSubmit') }}
</button>
<span class="m-2">/</span>
<button class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 disabled:bg-green-400"
:disabled="isSubmitting" type="button" @click="authStore.loginSSO()">
<span v-show="isSubmitting"
class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2 inline-block"></span>
{{ $t('common.VLoginSSOSubmit') }}
</button>
</div>

<!-- API Error Message -->
<div v-if="errors.apiError" class="bg-red-100 text-red-600 p-3 rounded mt-3">{{ errors.apiError }}</div>
</Form>

<!-- horizontal separation-->
<hr class="my-4" />

<!-- available SSO providers in config -->
<div v-if="configsStore.configs.loading" class="flex items-center justify-center my-4">
<div class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin">
<span class="sr-only">{{ $t('common.VLoginLoadingSSO') }}</span>
</div>
</div>
<div v-else>
<div v-if="configsStore.configs.sso_available_providers.length > 0" class="space-y-2 mb-4">
<div v-for="provider in configsStore.configs.sso_available_providers" :key="provider.provider">
<button @click="authStore.loginSSO(provider.provider)"
class="w-full flex items-center justify-center border border-gray-300 rounded px-4 py-2 hover:bg-gray-100">
<img :src="provider.icon_url" :alt="provider.display_name" class="h-6 w-6 mr-2" />
<span>{{ $t('common.VLoginSSOLoginWith', { provider: provider.display_name }) }}</span>
</button>
</div>
</div>
<div v-else class="text-gray-500 italic mb-4">{{ $t('common.VLoginNoSSOProviders') }}</div>
</div>

<!-- Links -->
<div class="mt-4 flex justify-between">
Expand Down
1 change: 1 addition & 0 deletions tests/electrostoreFRONT/cypress/e2e/login.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe("Login Page", () => {
"max_length_reason": 50,
"max_length_status": 50,
"max_size_document_in_mb": 5,
"sso_available_providers": [],// e.g : [{"provider":"authentik","display_name":"Authentik","icon_url":"https://example.com/icon.png"}]
},
}).as("getConfig");

Expand Down
Loading