Add UNIX.dog theme, no-JS account console
This commit is contained in:
parent
1ee98bbbe7
commit
5d9b49b47d
|
@ -22,6 +22,6 @@ package org.keycloak.forms.account;
|
|||
*/
|
||||
public enum AccountPages {
|
||||
|
||||
ACCOUNT, PASSWORD, TOTP, FEDERATED_IDENTITY, LOG, SESSIONS, APPLICATIONS, RESOURCES, RESOURCE_DETAIL;
|
||||
ACCOUNT, PASSWORD, AUTHENTICATION, FEDERATED_IDENTITY, LOG, SESSIONS, APPLICATIONS, RESOURCES, RESOURCE_DETAIL;
|
||||
|
||||
}
|
||||
|
|
|
@ -20,18 +20,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.events.Event;
|
||||
import org.keycloak.forms.account.AccountPages;
|
||||
import org.keycloak.forms.account.AccountProvider;
|
||||
import org.keycloak.forms.account.freemarker.model.AccountBean;
|
||||
import org.keycloak.forms.account.freemarker.model.AccountFederatedIdentityBean;
|
||||
import org.keycloak.forms.account.freemarker.model.ApplicationsBean;
|
||||
import org.keycloak.forms.account.freemarker.model.AuthorizationBean;
|
||||
import org.keycloak.forms.account.freemarker.model.FeaturesBean;
|
||||
import org.keycloak.forms.account.freemarker.model.LogBean;
|
||||
import org.keycloak.forms.account.freemarker.model.PasswordBean;
|
||||
import org.keycloak.forms.account.freemarker.model.RealmBean;
|
||||
import org.keycloak.forms.account.freemarker.model.ReferrerBean;
|
||||
import org.keycloak.forms.account.freemarker.model.SessionsBean;
|
||||
import org.keycloak.forms.account.freemarker.model.TotpBean;
|
||||
import org.keycloak.forms.account.freemarker.model.UrlBean;
|
||||
import org.keycloak.forms.account.freemarker.model.*;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -164,8 +153,8 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
attributes.put("account", new AccountBean(user, profileFormData));
|
||||
|
||||
switch (page) {
|
||||
case TOTP:
|
||||
attributes.put("totp", new TotpBean(session, realm, user, uriInfo.getRequestUriBuilder()));
|
||||
case AUTHENTICATION:
|
||||
attributes.put("authentication", new AuthenticationBean(session, realm, user, uriInfo));
|
||||
break;
|
||||
case FEDERATED_IDENTITY:
|
||||
attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
|
||||
|
|
|
@ -30,8 +30,8 @@ public class Templates {
|
|||
return "account.ftl";
|
||||
case PASSWORD:
|
||||
return "password.ftl";
|
||||
case TOTP:
|
||||
return "totp.ftl";
|
||||
case AUTHENTICATION:
|
||||
return "authentication.ftl";
|
||||
case FEDERATED_IDENTITY:
|
||||
return "federatedIdentity.ftl";
|
||||
case LOG:
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.forms.account.freemarker.model;
|
||||
|
||||
import org.keycloak.authentication.Authenticator;
|
||||
import org.keycloak.authentication.AuthenticatorFactory;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.credential.*;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.account.CredentialMetadataRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.models.AuthenticationExecutionModel.Requirement.DISABLED;
|
||||
import static org.keycloak.utils.CredentialHelper.createUserStorageCredentialRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nullobsi@unix.dog">Kayden Tebau</a>
|
||||
*/
|
||||
public class AuthenticationBean {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
private final UserModel user;
|
||||
private final AuthorizationProvider authorization;
|
||||
private final UriInfo uriInfo;
|
||||
|
||||
private final Map<String, CredentialTypeBean> credTypes;
|
||||
|
||||
private final Map<String, List<CredentialUserMetadataBean>> credentials;
|
||||
|
||||
public AuthenticationBean(KeycloakSession session, RealmModel realm, UserModel user, UriInfo uriInfo) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.user = user;
|
||||
this.uriInfo = uriInfo;
|
||||
authorization = session.getProvider(AuthorizationProvider.class);
|
||||
credTypes = findCredentialTypes();
|
||||
credentials = new HashMap<>();
|
||||
}
|
||||
|
||||
public static class CredentialUserMetadataBean {
|
||||
|
||||
}
|
||||
|
||||
public static class CredentialTypeBean {
|
||||
// ** category, displayName and helptext attributes can be ordinary UI text or a key into
|
||||
// a localized message bundle. Typically, it will be a key, but
|
||||
// the UI will work just fine if you don't care about localization
|
||||
// and you want to just send UI text.
|
||||
//
|
||||
// Also, the ${} shown in Apicurio is not needed.
|
||||
private String type;
|
||||
private String category; // **
|
||||
private String displayName;
|
||||
private String helptext; // **
|
||||
private String iconCssClass;
|
||||
private String createAction;
|
||||
private String updateAction;
|
||||
private boolean removable;
|
||||
private List<CredentialMetadataRepresentation> userCredentialMetadatas;
|
||||
private CredentialTypeMetadata metadata;
|
||||
|
||||
public CredentialTypeBean() {
|
||||
}
|
||||
|
||||
public CredentialTypeBean(CredentialTypeMetadata metadata, List<CredentialMetadataRepresentation> userCredentialMetadatas) {
|
||||
this.metadata = metadata;
|
||||
this.type = metadata.getType();
|
||||
this.category = metadata.getCategory().toString();
|
||||
this.displayName = metadata.getDisplayName();
|
||||
this.helptext = metadata.getHelpText();
|
||||
this.iconCssClass = metadata.getIconCssClass();
|
||||
this.createAction = metadata.getCreateAction();
|
||||
this.updateAction = metadata.getUpdateAction();
|
||||
this.removable = metadata.isRemoveable();
|
||||
this.userCredentialMetadatas = userCredentialMetadatas;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getHelpText() {
|
||||
return helptext;
|
||||
}
|
||||
|
||||
public String getIconCssClass() {
|
||||
return iconCssClass;
|
||||
}
|
||||
|
||||
public String getCreateAction() {
|
||||
return createAction;
|
||||
}
|
||||
|
||||
public String getUpdateAction() {
|
||||
return updateAction;
|
||||
}
|
||||
|
||||
public boolean getRemovable() {
|
||||
return removable;
|
||||
}
|
||||
|
||||
public List<CredentialMetadataRepresentation> getUserCredentialMetadatas() {
|
||||
return userCredentialMetadatas;
|
||||
}
|
||||
|
||||
public CredentialTypeMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
public List<CredentialTypeBean> getCredentialTypes() {
|
||||
return new ArrayList<>(credTypes.values());
|
||||
}
|
||||
|
||||
|
||||
private Map<String, CredentialTypeBean> findCredentialTypes() {
|
||||
List<CredentialProvider> credentialProviders = session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
||||
.filter(f -> Types.supports(CredentialProvider.class, f, CredentialProviderFactory.class))
|
||||
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
|
||||
.collect(Collectors.toList());
|
||||
Set<String> enabledCredentialTypes = getEnabledCredentialTypes(credentialProviders);
|
||||
|
||||
Stream<CredentialModel> modelsStream = user.credentialManager().getStoredCredentialsStream();
|
||||
List<CredentialModel> models = modelsStream.collect(Collectors.toList());
|
||||
|
||||
Function<CredentialProvider, CredentialTypeBean> toCredentialBean = (credentialProvider) -> {
|
||||
CredentialTypeMetadataContext ctx = CredentialTypeMetadataContext.builder()
|
||||
.user(user)
|
||||
.build(session);
|
||||
CredentialTypeMetadata metadata = credentialProvider.getCredentialTypeMetadata(ctx);
|
||||
|
||||
List<CredentialMetadataRepresentation> userCredentialMetadataModels = null;
|
||||
|
||||
List<CredentialModel> modelsOfType = models.stream()
|
||||
.filter(credentialModel -> credentialProvider.getType().equals(credentialModel.getType()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
List<CredentialMetadata> credentialMetadataList = modelsOfType.stream()
|
||||
.map(m -> {
|
||||
return credentialProvider.getCredentialMetadata(
|
||||
credentialProvider.getCredentialFromModel(m), metadata
|
||||
);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
credentialMetadataList.stream().forEach(md -> md.getCredentialModel().setSecretData(null));
|
||||
userCredentialMetadataModels = credentialMetadataList.stream().map(ModelToRepresentation::toRepresentation).collect(Collectors.toList());
|
||||
|
||||
if (userCredentialMetadataModels.isEmpty() &&
|
||||
user.credentialManager().isConfiguredFor(credentialProvider.getType())) {
|
||||
// In case user is federated in the userStorage, he may have credential configured on the userStorage side. We're
|
||||
// creating "dummy" credential representing the credential provided by userStorage
|
||||
CredentialMetadataRepresentation metadataRepresentation = new CredentialMetadataRepresentation();
|
||||
CredentialRepresentation credential = createUserStorageCredentialRepresentation(credentialProvider.getType());
|
||||
metadataRepresentation.setCredential(credential);
|
||||
userCredentialMetadataModels = Collections.singletonList(metadataRepresentation);
|
||||
}
|
||||
|
||||
// In case that there are no userCredentials AND there are not required actions for setup new credential,
|
||||
// we won't include credentialType as user won't be able to do anything with it
|
||||
if (userCredentialMetadataModels.isEmpty() && metadata.getCreateAction() == null && metadata.getUpdateAction() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CredentialTypeBean(metadata, userCredentialMetadataModels);
|
||||
};
|
||||
|
||||
return credentialProviders.stream()
|
||||
.filter(p -> enabledCredentialTypes.contains(p.getType()))
|
||||
.map(toCredentialBean)
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(CredentialTypeBean::getMetadata))
|
||||
.collect(Collectors.toMap(CredentialTypeBean::getType, Function.identity()));
|
||||
}
|
||||
|
||||
private Set<String> getEnabledCredentialTypes(List<CredentialProvider> credentialProviders) {
|
||||
Stream<String> enabledCredentialTypes = realm.getAuthenticationFlowsStream()
|
||||
.filter(((Predicate<AuthenticationFlowModel>) this::isFlowEffectivelyDisabled).negate())
|
||||
.flatMap(flow ->
|
||||
realm.getAuthenticationExecutionsStream(flow.getId())
|
||||
.filter(exe -> Objects.nonNull(exe.getAuthenticator()) && exe.getRequirement() != DISABLED)
|
||||
.map(exe -> (AuthenticatorFactory) session.getKeycloakSessionFactory()
|
||||
.getProviderFactory(Authenticator.class, exe.getAuthenticator()))
|
||||
.filter(Objects::nonNull)
|
||||
.map(AuthenticatorFactory::getReferenceCategory)
|
||||
.filter(Objects::nonNull)
|
||||
);
|
||||
|
||||
Set<String> credentialTypes = credentialProviders.stream()
|
||||
.map(CredentialProvider::getType)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return enabledCredentialTypes.filter(credentialTypes::contains).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
// Returns true if flow is effectively disabled - either it's execution or some parent execution is disabled
|
||||
private boolean isFlowEffectivelyDisabled(AuthenticationFlowModel flow) {
|
||||
while (!flow.isTopLevel()) {
|
||||
AuthenticationExecutionModel flowExecution = realm.getAuthenticationExecutionByFlowId(flow.getId());
|
||||
if (flowExecution == null) return false; // Can happen under some corner cases
|
||||
if (DISABLED == flowExecution.getRequirement()) return true;
|
||||
if (flowExecution.getParentFlow() == null) return false;
|
||||
|
||||
// Check parent flow
|
||||
flow = realm.getAuthenticationFlowById(flowExecution.getParentFlow());
|
||||
if (flow == null) return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.forms.account.freemarker.model;
|
||||
|
||||
import org.keycloak.authentication.otp.OTPApplicationProvider;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.HmacOTP;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.utils.TotpUtils;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.utils.CredentialHelper.createUserStorageCredentialRepresentation;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class TotpBean {
|
||||
|
||||
private final RealmModel realm;
|
||||
private final String totpSecret;
|
||||
private final String totpSecretEncoded;
|
||||
private final String totpSecretQrCode;
|
||||
private final boolean enabled;
|
||||
private KeycloakSession session;
|
||||
private final UriBuilder uriBuilder;
|
||||
private final List<CredentialModel> otpCredentials;
|
||||
private final List<String> supportedApplications;
|
||||
|
||||
public TotpBean(KeycloakSession session, RealmModel realm, UserModel user, UriBuilder uriBuilder) {
|
||||
this.session = session;
|
||||
this.uriBuilder = uriBuilder;
|
||||
this.enabled = user.credentialManager().isConfiguredFor(OTPCredentialModel.TYPE);
|
||||
if (enabled) {
|
||||
List<CredentialModel> otpCredentials = user.credentialManager().getStoredCredentialsByTypeStream(OTPCredentialModel.TYPE).collect(Collectors.toList());
|
||||
|
||||
if (otpCredentials.isEmpty()) {
|
||||
// Credential is configured on userStorage side. Create the "fake" credential similar like we do for the new account console
|
||||
CredentialRepresentation credential = createUserStorageCredentialRepresentation(OTPCredentialModel.TYPE);
|
||||
this.otpCredentials = Collections.singletonList(RepresentationToModel.toModel(credential));
|
||||
} else {
|
||||
this.otpCredentials = otpCredentials;
|
||||
}
|
||||
} else {
|
||||
this.otpCredentials = Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
this.realm = realm;
|
||||
this.totpSecret = HmacOTP.generateSecret(20);
|
||||
this.totpSecretEncoded = TotpUtils.encode(totpSecret);
|
||||
this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
|
||||
|
||||
OTPPolicy otpPolicy = realm.getOTPPolicy();
|
||||
this.supportedApplications = session.getAllProviders(OTPApplicationProvider.class).stream()
|
||||
.filter(p -> p.supports(otpPolicy))
|
||||
.map(OTPApplicationProvider::getName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getTotpSecret() {
|
||||
return totpSecret;
|
||||
}
|
||||
|
||||
public String getTotpSecretEncoded() {
|
||||
return totpSecretEncoded;
|
||||
}
|
||||
|
||||
public String getTotpSecretQrCode() {
|
||||
return totpSecretQrCode;
|
||||
}
|
||||
|
||||
public String getManualUrl() {
|
||||
return uriBuilder.replaceQueryParam("mode", "manual").build().toString();
|
||||
}
|
||||
|
||||
public String getQrUrl() {
|
||||
return uriBuilder.replaceQueryParam("mode", "qr").build().toString();
|
||||
}
|
||||
|
||||
public OTPPolicy getPolicy() {
|
||||
return realm.getOTPPolicy();
|
||||
}
|
||||
|
||||
public List<String> getSupportedApplications() {
|
||||
return supportedApplications;
|
||||
}
|
||||
|
||||
public List<CredentialModel> getOtpCredentials() {
|
||||
return otpCredentials;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -63,8 +63,8 @@ public class UrlBean {
|
|||
return Urls.accountFederatedIdentityPage(baseQueryURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getTotpUrl() {
|
||||
return Urls.accountTotpPage(baseQueryURI, realm).toString();
|
||||
public String getAuthenticationUrl() {
|
||||
return Urls.accountAuthenticationPage(baseQueryURI, realm).toString();
|
||||
}
|
||||
|
||||
public String getLogUrl() {
|
||||
|
|
|
@ -129,8 +129,8 @@ public class Urls {
|
|||
.build(realmName);
|
||||
}
|
||||
|
||||
public static URI accountTotpPage(URI baseUri, String realmName) {
|
||||
return accountBase(baseUri).path(AccountFormService.class, "totpPage").build(realmName);
|
||||
public static URI accountAuthenticationPage(URI baseUri, String realmName) {
|
||||
return accountBase(baseUri).path(AccountFormService.class, "authenticationPage").build(realmName);
|
||||
}
|
||||
|
||||
public static URI accountLogPage(URI baseUri, String realmName) {
|
||||
|
|
|
@ -131,6 +131,10 @@ public abstract class AbstractSecuredLocalService {
|
|||
protected abstract URI getBaseRedirectUri();
|
||||
|
||||
protected Response login(String path) {
|
||||
return login(path, null);
|
||||
}
|
||||
|
||||
protected Response login(String path, String action) {
|
||||
OAuthRedirect oauth = new OAuthRedirect();
|
||||
String authUrl = OIDCLoginProtocolService.authUrl(session.getContext().getUri()).build(realm.getName()).toString();
|
||||
oauth.setAuthUrl(authUrl);
|
||||
|
@ -158,7 +162,7 @@ public abstract class AbstractSecuredLocalService {
|
|||
URI accountUri = uriBuilder.build(realm.getName());
|
||||
|
||||
oauth.setStateCookiePath(accountUri.getRawPath());
|
||||
return oauth.redirect(session.getContext().getUri(), accountUri.toString());
|
||||
return oauth.redirect(session.getContext().getUri(), accountUri.toString(), action);
|
||||
}
|
||||
|
||||
static class OAuthRedirect extends AbstractOAuthClient {
|
||||
|
@ -170,6 +174,10 @@ public abstract class AbstractSecuredLocalService {
|
|||
}
|
||||
|
||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||
return redirect(uriInfo, redirectUri, null);
|
||||
}
|
||||
|
||||
public Response redirect(UriInfo uriInfo, String redirectUri, String kcAction) {
|
||||
String state = getStateCode();
|
||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||
|
||||
|
@ -180,6 +188,10 @@ public abstract class AbstractSecuredLocalService {
|
|||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||
|
||||
if (kcAction != null) {
|
||||
uriBuilder.queryParam("kc_action", kcAction);
|
||||
}
|
||||
|
||||
URI url = uriBuilder.build();
|
||||
|
||||
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.common.Profile;
|
|||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.Event;
|
||||
|
@ -48,14 +49,11 @@ import org.keycloak.models.ClientSessionContext;
|
|||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
|
@ -83,7 +81,6 @@ import org.keycloak.userprofile.UserProfile;
|
|||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.userprofile.EventAuditingAttributeChangeListener;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.utils.CredentialHelper;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
|
@ -270,15 +267,15 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||
return forwardToPage(null, AccountPages.ACCOUNT);
|
||||
}
|
||||
|
||||
public static UriBuilder totpUrl(UriBuilder base) {
|
||||
return RealmsResource.accountUrl(base).path(AccountFormService.class, "totpPage");
|
||||
public static UriBuilder authenticationUrl(UriBuilder base) {
|
||||
return RealmsResource.accountUrl(base).path(AccountFormService.class, "authenticationPage");
|
||||
}
|
||||
|
||||
@Path("totp")
|
||||
@Path("authentication")
|
||||
@GET
|
||||
public Response totpPage() {
|
||||
public Response authenticationPage() {
|
||||
account.setAttribute("mode", session.getContext().getUri().getQueryParameters().getFirst("mode"));
|
||||
return forwardToPage("totp", AccountPages.TOTP);
|
||||
return forwardToPage("authentication", AccountPages.AUTHENTICATION);
|
||||
}
|
||||
|
||||
public static UriBuilder passwordUrl(UriBuilder base) {
|
||||
|
@ -495,24 +492,22 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("totp")
|
||||
@Path("authentication")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response processTotpUpdate() {
|
||||
public Response processAuthenticationUpdate() {
|
||||
MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
|
||||
|
||||
if (auth == null) {
|
||||
return login("totp");
|
||||
return login("authentication");
|
||||
}
|
||||
|
||||
auth.require(AccountRoles.MANAGE_ACCOUNT);
|
||||
|
||||
account.setAttribute("mode", session.getContext().getUri().getQueryParameters().getFirst("mode"));
|
||||
|
||||
String action = formData.getFirst("submitAction");
|
||||
if (action != null && action.equals("Cancel")) {
|
||||
setReferrerOnPage();
|
||||
return account.createResponse(AccountPages.TOTP);
|
||||
return account.createResponse(AccountPages.AUTHENTICATION);
|
||||
}
|
||||
|
||||
csrfCheck(formData);
|
||||
|
@ -521,37 +516,37 @@ public class AccountFormService extends AbstractSecuredLocalService {
|
|||
|
||||
if (action != null && action.equals("Delete")) {
|
||||
String credentialId = formData.getFirst("credentialId");
|
||||
if (credentialId == null) {
|
||||
String credType = formData.getFirst("credentialType");
|
||||
if (credentialId == null || credType == null) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST).createResponse(AccountPages.TOTP);
|
||||
return account.setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST).createResponse(AccountPages.AUTHENTICATION);
|
||||
}
|
||||
CredentialHelper.deleteOTPCredential(session, realm, user, credentialId);
|
||||
event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
CredentialProvider credentialProvider = session.getProvider(CredentialProvider.class, credType);
|
||||
if (credentialProvider == null) {
|
||||
setReferrerOnPage();
|
||||
return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.TOTP);
|
||||
return account.setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST).createResponse(AccountPages.AUTHENTICATION);
|
||||
}
|
||||
boolean removed = credentialProvider.deleteCredential(realm, user, credentialId);
|
||||
if (!removed) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST).createResponse(AccountPages.AUTHENTICATION);
|
||||
}
|
||||
|
||||
// TODO: event???
|
||||
// event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
// TODO: string key
|
||||
setReferrerOnPage();
|
||||
return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.AUTHENTICATION);
|
||||
} else {
|
||||
String challengeResponse = formData.getFirst("totp");
|
||||
String totpSecret = formData.getFirst("totpSecret");
|
||||
String userLabel = formData.getFirst("userLabel");
|
||||
|
||||
OTPPolicy policy = realm.getOTPPolicy();
|
||||
OTPCredentialModel credentialModel = OTPCredentialModel.createFromPolicy(realm, totpSecret, userLabel);
|
||||
if (Validation.isBlank(challengeResponse)) {
|
||||
String kcAction = formData.getFirst("kcAction");
|
||||
if (kcAction == null) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Status.OK, Messages.MISSING_TOTP).createResponse(AccountPages.TOTP);
|
||||
} else if (!CredentialValidation.validOTP(challengeResponse, credentialModel, policy.getLookAheadWindow())) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Status.OK, Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
|
||||
return account.setError(Status.OK, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST).createResponse(AccountPages.AUTHENTICATION);
|
||||
}
|
||||
|
||||
if (!CredentialHelper.createOTPCredential(session, realm, user, challengeResponse, credentialModel)) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Status.OK, Messages.INVALID_TOTP).createResponse(AccountPages.TOTP);
|
||||
}
|
||||
event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
setReferrerOnPage();
|
||||
return account.setSuccess(Messages.SUCCESS_TOTP).createResponse(AccountPages.TOTP);
|
||||
// Do action with kc_action param
|
||||
return login("authentication", kcAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,5 +8,8 @@
|
|||
}, {
|
||||
"name" : "keycloak.v2",
|
||||
"types": [ "account", "admin" ]
|
||||
}, {
|
||||
"name" : "unixdog",
|
||||
"types": [ "account", "login" ]
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='account' bodyClass='user'; section>
|
||||
<h1>${msg("editAccountHtmlTitle")}</h1>
|
||||
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
|
||||
|
||||
<form action="${url.accountUrl}" class="form-horizontal" method="post">
|
||||
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
<div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
|
||||
<label for="username" class="control-label">${msg("username")}</label> <#if realm.editUsernameAllowed><span class="required">*</span></#if>
|
||||
<input type="text" class="form-control" id="username" name="username" <#if !realm.editUsernameAllowed>disabled="disabled"</#if> value="${(account.username!'')}"/>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
|
||||
<label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
|
||||
<input type="text" class="form-control" id="email" name="email" autofocus value="${(account.email!'')}"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
|
||||
<label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
|
||||
<input type="text" class="form-control" id="firstName" name="firstName" value="${(account.firstName!'')}"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
|
||||
<label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
|
||||
<input type="text" class="form-control" id="lastName" name="lastName" value="${(account.lastName!'')}"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<#if url.referrerURI??><a href="${url.referrerURI}">${kcSanitize(msg("backToApplication")?no_esc)}</a></#if>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
|
||||
</div>
|
||||
</form>
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,72 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='applications' bodyClass='applications'; section>
|
||||
|
||||
<h2>${msg("applicationsHtmlTitle")}</h2>
|
||||
|
||||
<form action="${url.applicationsUrl}" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" id="referrer" name="referrer" value="${stateChecker}">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("application")}</th>
|
||||
<th>${msg("availableRoles")}</th>
|
||||
<th>${msg("grantedPermissions")}</th>
|
||||
<th>${msg("additionalGrants")}</th>
|
||||
<th>${msg("action")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<#list applications.applications as application>
|
||||
<tr>
|
||||
<td>
|
||||
<#if application.effectiveUrl?has_content><a href="${application.effectiveUrl}"></#if>
|
||||
<#if application.client.name?has_content>${advancedMsg(application.client.name)}<#else>${application.client.clientId}</#if>
|
||||
<#if application.effectiveUrl?has_content></a></#if>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<#list application.realmRolesAvailable as role>
|
||||
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
|
||||
<#if role_has_next>, </#if>
|
||||
</#list>
|
||||
<#list application.resourceRolesAvailable?keys as resource>
|
||||
<#if application.realmRolesAvailable?has_content>, </#if>
|
||||
<#list application.resourceRolesAvailable[resource] as clientRole>
|
||||
<#if clientRole.roleDescription??>${advancedMsg(clientRole.roleDescription)}<#else>${advancedMsg(clientRole.roleName)}</#if>
|
||||
${msg("inResource")} <strong><#if clientRole.clientName??>${advancedMsg(clientRole.clientName)}<#else>${clientRole.clientId}</#if></strong>
|
||||
<#if clientRole_has_next>, </#if>
|
||||
</#list>
|
||||
</#list>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<#if application.client.consentRequired>
|
||||
<#list application.clientScopesGranted as claim>
|
||||
${advancedMsg(claim)}<#if claim_has_next>, </#if>
|
||||
</#list>
|
||||
<#else>
|
||||
<strong>${msg("fullAccess")}</strong>
|
||||
</#if>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<#list application.additionalGrants as grant>
|
||||
${advancedMsg(grant)}<#if grant_has_next>, </#if>
|
||||
</#list>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<#if (application.client.consentRequired && application.clientScopesGranted?has_content) || application.additionalGrants?has_content>
|
||||
<button type='submit' class='${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!}' id='revoke-${application.client.clientId}' name='clientId' value="${application.client.id}">${msg("revoke")}</button>
|
||||
</#if>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,58 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='authentication' bodyClass='authentication'; section>
|
||||
|
||||
<h1>${msg("authenticationTitle")}</h1>
|
||||
|
||||
<#list authentication.credentialTypes as credType>
|
||||
<section>
|
||||
<h2>${msg(credType.displayName)}</h2>
|
||||
<p>${msg(credType.helpText!"default-help-text")}</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#list credType.userCredentialMetadatas as userData>
|
||||
<tr>
|
||||
<td>${userData.credential.userLabel!"No label"}</td>
|
||||
<td>${userData.credential.createdDate?number_to_datetime!"No date"}</td>
|
||||
<td>
|
||||
<#if credType.removable!false>
|
||||
<form action="${url.authenticationUrl}" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" name="submitAction" value="Delete">
|
||||
<input type="hidden" name="credentialId" value="${userData.credential.id}">
|
||||
<input type="submit" value="Delete">
|
||||
</form>
|
||||
</#if>
|
||||
<#if credType.updateAction??>
|
||||
<form action="${url.authenticationUrl}" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" name="submitAction" value="Update">
|
||||
<input type="hidden" name="kcAction" value="${credType.updateAction}">
|
||||
<input type="hidden" name="credentialId" value="${userData.credential.id}">
|
||||
<input type="submit" value="Update">
|
||||
</form>
|
||||
</#if>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
<#if credType.createAction??>
|
||||
<form action="${url.authenticationUrl}" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" name="submitAction" value="Create">
|
||||
<input type="hidden" name="kcAction" value="${credType.createAction}">
|
||||
<input type="submit" value="Create">
|
||||
</form>
|
||||
</#if>
|
||||
</section>
|
||||
</#list>
|
||||
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,42 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='social' bodyClass='social'; section>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h2>${msg("federatedIdentitiesHtmlTitle")}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="federated-identities">
|
||||
<#list federatedIdentity.identities as identity>
|
||||
<div class="row margin-bottom">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="${identity.providerId!}" class="control-label">${identity.displayName!}</label>
|
||||
</div>
|
||||
<div class="col-sm-5 col-md-5">
|
||||
<input disabled="true" class="form-control" value="${identity.userName!}">
|
||||
</div>
|
||||
<div class="col-sm-5 col-md-5">
|
||||
<#if identity.connected>
|
||||
<#if federatedIdentity.removeLinkPossible>
|
||||
<form action="${url.socialUrl}" method="post" class="form-inline">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" id="action" name="action" value="remove">
|
||||
<input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
|
||||
<button id="remove-link-${identity.providerId!}" class="btn btn-default">${msg("doRemove")}</button>
|
||||
</form>
|
||||
</#if>
|
||||
<#else>
|
||||
<form action="${url.socialUrl}" method="post" class="form-inline">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input type="hidden" id="action" name="action" value="add">
|
||||
<input type="hidden" id="providerId" name="providerId" value="${identity.providerId!}">
|
||||
<button id="add-link-${identity.providerId!}" class="btn btn-default">${msg("doAdd")}</button>
|
||||
</form>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,35 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='log' bodyClass='log'; section>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h2>${msg("accountLogHtmlTitle")}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>${msg("date")}</td>
|
||||
<td>${msg("event")}</td>
|
||||
<td>${msg("ip")}</td>
|
||||
<td>${msg("client")}</td>
|
||||
<td>${msg("details")}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<#list log.events as event>
|
||||
<tr>
|
||||
<td>${event.date?datetime}</td>
|
||||
<td>${event.event}</td>
|
||||
<td>${event.ipAddress}</td>
|
||||
<td>${event.client!}</td>
|
||||
<td><#list event.details as detail>${detail.key} = ${detail.value} <#if detail_has_next>, </#if></#list></td>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,441 @@
|
|||
doSave=Save
|
||||
doCancel=Cancel
|
||||
doLogOutAllSessions=Log out all sessions
|
||||
doRemove=Remove
|
||||
doAdd=Add
|
||||
doSignOut=Sign out
|
||||
doLogIn=Log In
|
||||
doLink=Link
|
||||
noAccessMessage=Access not allowed
|
||||
|
||||
personalInfoSidebarTitle=Personal info
|
||||
accountSecuritySidebarTitle=Account security
|
||||
signingInSidebarTitle=Signing in
|
||||
deviceActivitySidebarTitle=Device activity
|
||||
linkedAccountsSidebarTitle=Linked accounts
|
||||
|
||||
editAccountHtmlTitle=Edit Account
|
||||
personalInfoHtmlTitle=Personal Info
|
||||
federatedIdentitiesHtmlTitle=Federated Identities
|
||||
accountLogHtmlTitle=Account Log
|
||||
changePasswordHtmlTitle=Change Password
|
||||
deviceActivityHtmlTitle=Device Activity
|
||||
sessionsHtmlTitle=Sessions
|
||||
accountManagementTitle=Keycloak Account Management
|
||||
authenticatorTitle=Authenticator
|
||||
applicationsHtmlTitle=Applications
|
||||
linkedAccountsHtmlTitle=Linked accounts
|
||||
|
||||
accountManagementWelcomeMessage=Welcome to Keycloak Account Management
|
||||
personalInfoIntroMessage=Manage your basic information
|
||||
accountSecurityTitle=Account Security
|
||||
accountSecurityIntroMessage=Control your password and account access
|
||||
applicationsIntroMessage=Track and manage your app permission to access your account
|
||||
resourceIntroMessage=Share your resources among team members
|
||||
passwordLastUpdateMessage=Your password was updated at
|
||||
updatePasswordTitle=Update Password
|
||||
updatePasswordMessageTitle=Make sure you choose a strong password
|
||||
updatePasswordMessage=A strong password contains a mix of numbers, letters, and symbols. It is hard to guess, does not resemble a real word, and is only used for this account.
|
||||
personalSubTitle=Your Personal Info
|
||||
personalSubMessage=Manage your basic information.
|
||||
|
||||
authenticatorCode=One-time code
|
||||
email=Email
|
||||
firstName=First name
|
||||
givenName=Given name
|
||||
fullName=Full name
|
||||
lastName=Last name
|
||||
familyName=Family name
|
||||
password=Password
|
||||
currentPassword=Current Password
|
||||
passwordConfirm=Confirmation
|
||||
passwordNew=New Password
|
||||
username=Username
|
||||
address=Address
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
emailVerified=Email verified
|
||||
website=Web page
|
||||
phoneNumber=Phone number
|
||||
phoneNumberVerified=Phone number verified
|
||||
gender=Gender
|
||||
birthday=Birthdate
|
||||
zoneinfo=Time zone
|
||||
gssDelegationCredential=GSS Delegation Credential
|
||||
|
||||
profileScopeConsentText=User profile
|
||||
emailScopeConsentText=Email address
|
||||
addressScopeConsentText=Address
|
||||
phoneScopeConsentText=Phone number
|
||||
offlineAccessScopeConsentText=Offline Access
|
||||
samlRoleListScopeConsentText=My Roles
|
||||
rolesScopeConsentText=User roles
|
||||
|
||||
role_admin=Admin
|
||||
role_realm-admin=Realm Admin
|
||||
role_create-realm=Create realm
|
||||
role_view-realm=View realm
|
||||
role_view-users=View users
|
||||
role_view-applications=View applications
|
||||
role_view-groups=View groups
|
||||
role_view-clients=View clients
|
||||
role_view-events=View events
|
||||
role_view-identity-providers=View identity providers
|
||||
role_view-consent=View consents
|
||||
role_manage-realm=Manage realm
|
||||
role_manage-users=Manage users
|
||||
role_manage-applications=Manage applications
|
||||
role_manage-identity-providers=Manage identity providers
|
||||
role_manage-clients=Manage clients
|
||||
role_manage-events=Manage events
|
||||
role_view-profile=View profile
|
||||
role_manage-account=Manage account
|
||||
role_manage-account-links=Manage account links
|
||||
role_manage-consent=Manage consents
|
||||
role_read-token=Read token
|
||||
role_offline-access=Offline access
|
||||
role_uma_authorization=Obtain permissions
|
||||
client_account=Account
|
||||
client_account-console=Account Console
|
||||
client_security-admin-console=security admin console
|
||||
client_admin-cli=Admin CLI
|
||||
client_realm-management=Realm Management
|
||||
client_broker=Broker
|
||||
|
||||
|
||||
requiredFields=Required fields
|
||||
allFieldsRequired=All fields required
|
||||
|
||||
backToApplication=« Back to application
|
||||
backTo=Back to {0}
|
||||
|
||||
date=Date
|
||||
event=Event
|
||||
ip=IP
|
||||
client=Client
|
||||
clients=Clients
|
||||
details=Details
|
||||
started=Started
|
||||
lastAccess=Last Access
|
||||
expires=Expires
|
||||
applications=Applications
|
||||
|
||||
account=Account
|
||||
federatedIdentity=Federated Identity
|
||||
authenticator=Authenticator
|
||||
device-activity=Device activity
|
||||
sessions=Sessions
|
||||
log=Log
|
||||
|
||||
application=Application
|
||||
availableRoles=Available Roles
|
||||
grantedPermissions=Granted Permissions
|
||||
grantedPersonalInfo=Granted Personal Info
|
||||
additionalGrants=Additional Grants
|
||||
action=Action
|
||||
inResource=in
|
||||
fullAccess=Full Access
|
||||
offlineToken=Offline Token
|
||||
revoke=Revoke Grant
|
||||
|
||||
configureAuthenticators=Configured Authenticators
|
||||
mobile=Mobile
|
||||
totpStep1=Install one of the following applications on your mobile:
|
||||
totpStep2=Open the application and scan the barcode:
|
||||
totpStep3=Enter the one-time code provided by the application and click Save to finish the setup.
|
||||
totpStep3DeviceName=Provide a Device Name to help you manage your OTP devices.
|
||||
|
||||
totpManualStep2=Open the application and enter the key:
|
||||
totpManualStep3=Use the following configuration values if the application allows setting them:
|
||||
totpUnableToScan=Unable to scan?
|
||||
totpScanBarcode=Scan barcode?
|
||||
|
||||
totp.totp=Time-based
|
||||
totp.hotp=Counter-based
|
||||
|
||||
totpType=Type
|
||||
totpAlgorithm=Algorithm
|
||||
totpDigits=Digits
|
||||
totpInterval=Interval
|
||||
totpCounter=Counter
|
||||
totpDeviceName=Device Name
|
||||
|
||||
totpAppFreeOTPName=FreeOTP
|
||||
totpAppGoogleName=Google Authenticator
|
||||
totpAppMicrosoftAuthenticatorName=Microsoft Authenticator
|
||||
|
||||
irreversibleAction=This action is irreversible
|
||||
deletingImplies=Deleting your account implies:
|
||||
errasingData=Erasing all your data
|
||||
loggingOutImmediately=Logging you out immediately
|
||||
accountUnusable=Any subsequent use of the application will not be possible with this account
|
||||
|
||||
missingUsernameMessage=Please specify username.
|
||||
missingFirstNameMessage=Please specify first name.
|
||||
invalidEmailMessage=Invalid email address.
|
||||
missingLastNameMessage=Please specify last name.
|
||||
missingEmailMessage=Please specify email.
|
||||
missingPasswordMessage=Please specify password.
|
||||
notMatchPasswordMessage=Passwords don''t match.
|
||||
invalidUserMessage=Invalid user
|
||||
updateReadOnlyAttributesRejectedMessage=Update of read-only attribute rejected
|
||||
|
||||
missingTotpMessage=Please specify authenticator code.
|
||||
missingTotpDeviceNameMessage=Please specify device name.
|
||||
invalidPasswordExistingMessage=Invalid existing password.
|
||||
invalidPasswordConfirmMessage=Password confirmation doesn''t match.
|
||||
invalidTotpMessage=Invalid authenticator code.
|
||||
|
||||
usernameExistsMessage=Username already exists.
|
||||
emailExistsMessage=Email already exists.
|
||||
|
||||
readOnlyUserMessage=You can''t update your account as it is read-only.
|
||||
readOnlyUsernameMessage=You can''t update your username as it is read-only.
|
||||
readOnlyPasswordMessage=You can''t update your password as your account is read-only.
|
||||
|
||||
successTotpMessage=Mobile authenticator configured.
|
||||
successTotpRemovedMessage=Mobile authenticator removed.
|
||||
|
||||
successGrantRevokedMessage=Grant revoked successfully.
|
||||
|
||||
accountUpdatedMessage=Your account has been updated.
|
||||
accountPasswordUpdatedMessage=Your password has been updated.
|
||||
|
||||
missingIdentityProviderMessage=Identity provider not specified.
|
||||
invalidFederatedIdentityActionMessage=Invalid or missing action.
|
||||
identityProviderNotFoundMessage=Specified identity provider not found.
|
||||
federatedIdentityLinkNotActiveMessage=This identity is not active anymore.
|
||||
federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have a password.
|
||||
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
|
||||
identityProviderRemovedMessage=Identity provider removed successfully.
|
||||
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
|
||||
staleCodeAccountMessage=The page expired. Please try one more time.
|
||||
consentDenied=Consent denied.
|
||||
access-denied-when-idp-auth=Access denied when authenticating with {0}
|
||||
|
||||
accountDisabledMessage=Account is disabled, contact your administrator.
|
||||
|
||||
accountTemporarilyDisabledMessage=Account is temporarily disabled, contact your administrator or try again later.
|
||||
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
|
||||
invalidPasswordMaxLengthMessage=Invalid password: maximum length {0}.
|
||||
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
|
||||
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
|
||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
||||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
|
||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
|
||||
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
|
||||
|
||||
# Authorization
|
||||
myResources=My Resources
|
||||
myResourcesSub=My resources
|
||||
doDeny=Deny
|
||||
doRevoke=Revoke
|
||||
doApprove=Approve
|
||||
doRemoveSharing=Remove Sharing
|
||||
doRemoveRequest=Remove Request
|
||||
peopleAccessResource=People with access to this resource
|
||||
resourceManagedPolicies=Permissions granting access to this resource
|
||||
resourceNoPermissionsGrantingAccess=No permissions granting access to this resource
|
||||
anyAction=Any action
|
||||
description=Description
|
||||
name=Name
|
||||
scopes=Scopes
|
||||
resource=Resource
|
||||
user=User
|
||||
peopleSharingThisResource=People sharing this resource
|
||||
shareWithOthers=Share with others
|
||||
needMyApproval=Need my approval
|
||||
requestsWaitingApproval=Your requests waiting approval
|
||||
icon=Icon
|
||||
requestor=Requestor
|
||||
owner=Owner
|
||||
resourcesSharedWithMe=Resources shared with me
|
||||
permissionRequestion=Permission Requestion
|
||||
permission=Permission
|
||||
shares=share(s)
|
||||
notBeingShared=This resource is not being shared.
|
||||
notHaveAnyResource=You don't have any resources
|
||||
noResourcesSharedWithYou=There are no resources shared with you
|
||||
havePermissionRequestsWaitingForApproval=You have {0} permission request(s) waiting for approval.
|
||||
clickHereForDetails=Click here for details.
|
||||
resourceIsNotBeingShared=The resource is not being shared
|
||||
|
||||
locale_ar=\u0639\u0631\u0628\u064A
|
||||
locale_ca=Catal\u00e0
|
||||
locale_cs=\u010Ce\u0161tina
|
||||
locale_de=Deutsch
|
||||
locale_en=English
|
||||
locale_es=Espa\u00f1ol
|
||||
locale_fr=Fran\u00e7ais
|
||||
locale_hu=Magyar
|
||||
locale_it=Italiano
|
||||
locale_ja=\u65e5\u672c\u8a9e
|
||||
locale_lt=Lietuvi\u0173
|
||||
locale_nl=Nederlands
|
||||
locale_no=Norsk
|
||||
locale_pl=Polski
|
||||
locale_pt-BR=Portugu\u00eas (Brasil)
|
||||
locale_ru=\u0420\u0443\u0441\u0441\u043a\u0438\u0439
|
||||
locale_sk=Sloven\u010dina
|
||||
locale_sv=Svenska
|
||||
locale_tr=T\u00FCrk\u00E7e
|
||||
locale_zh-CN=\u4e2d\u6587\u7b80\u4f53
|
||||
locale_fi=Suomi
|
||||
|
||||
# Applications
|
||||
applicationName=Name
|
||||
applicationType=Application Type
|
||||
applicationInUse=In-use app only
|
||||
clearAllFilter=Clear all filters
|
||||
activeFilters=Active filters
|
||||
filterByName=Filter By Name ...
|
||||
allApps=All applications
|
||||
internalApps=Internal applications
|
||||
thirdpartyApps=Third-Party applications
|
||||
appResults=Results
|
||||
clientNotFoundMessage=Client not found.
|
||||
|
||||
# Authentication
|
||||
authentication=Authentication
|
||||
authenticationTitle=Authentication
|
||||
|
||||
# Linked account
|
||||
authorizedProvider=Authorized Provider
|
||||
authorizedProviderMessage=Authorized Providers linked with your account
|
||||
identityProvider=Identity Provider
|
||||
identityProviderMessage=To link your account with identity providers you have configured
|
||||
socialLogin=Social Login
|
||||
userDefined=User Defined
|
||||
removeAccess=Remove Access
|
||||
removeAccessMessage=You will need to grant access again, if you want to use this app account.
|
||||
|
||||
#Authenticator
|
||||
authenticatorStatusMessage=Two-factor authentication is currently
|
||||
authenticatorFinishSetUpTitle=Your Two-Factor Authentication
|
||||
authenticatorFinishSetUpMessage=Each time you sign in to your Keycloak account, you will be asked to provide a two-factor authentication code.
|
||||
authenticatorSubTitle=Set Up Two-Factor Authentication
|
||||
authenticatorSubMessage=To enhance the security of your account, enable at least one of the available two-factor authentication methods.
|
||||
authenticatorMobileTitle=Mobile Authenticator
|
||||
authenticatorMobileMessage=Use mobile Authenticator to get Verification codes as the two-factor authentication.
|
||||
authenticatorMobileFinishSetUpMessage=The authenticator has been bound to your phone.
|
||||
authenticatorActionSetup=Set up
|
||||
authenticatorSMSTitle=SMS Code
|
||||
authenticatorSMSMessage=Keycloak will send the Verification code to your phone as the two-factor authentication.
|
||||
authenticatorSMSFinishSetUpMessage=Text messages are sent to
|
||||
authenticatorDefaultStatus=Default
|
||||
authenticatorChangePhone=Change Phone Number
|
||||
|
||||
#Authenticator - Mobile Authenticator setup
|
||||
authenticatorMobileSetupTitle=Mobile Authenticator Setup
|
||||
smscodeIntroMessage=Enter your phone number and a verification code will be sent to your phone.
|
||||
mobileSetupStep1=Install an authenticator application on your phone. The applications listed here are supported.
|
||||
mobileSetupStep2=Open the application and scan the barcode:
|
||||
mobileSetupStep3=Enter the one-time code provided by the application and click Save to finish the setup.
|
||||
scanBarCode=Want to scan the barcode?
|
||||
enterBarCode=Enter the one-time code
|
||||
doCopy=Copy
|
||||
doFinish=Finish
|
||||
|
||||
#Authenticator - SMS Code setup
|
||||
authenticatorSMSCodeSetupTitle=SMS Code Setup
|
||||
chooseYourCountry=Choose your country
|
||||
enterYourPhoneNumber=Enter your phone number
|
||||
sendVerficationCode=Send Verification Code
|
||||
enterYourVerficationCode=Enter your verification code
|
||||
|
||||
#Authenticator - backup Code setup
|
||||
authenticatorBackupCodesSetupTitle=Recovery Authentication Codes Setup
|
||||
realmName=Realm
|
||||
doDownload=Download
|
||||
doPrint=Print
|
||||
generateNewBackupCodes=Generate New Recovery Authentication Codes
|
||||
backtoAuthenticatorPage=Back to Authenticator Page
|
||||
|
||||
|
||||
#Resources
|
||||
resources=Resources
|
||||
sharedwithMe=Shared with Me
|
||||
share=Share
|
||||
sharedwith=Shared with
|
||||
accessPermissions=Access Permissions
|
||||
permissionRequests=Permission Requests
|
||||
approve=Approve
|
||||
approveAll=Approve all
|
||||
people=people
|
||||
perPage=per page
|
||||
currentPage=Current Page
|
||||
sharetheResource=Share the resource
|
||||
group=Group
|
||||
selectPermission=Select Permission
|
||||
addPeople=Add people to share your resource with
|
||||
addTeam=Add team to share your resource with
|
||||
myPermissions=My Permissions
|
||||
waitingforApproval=Waiting for approval
|
||||
anyPermission=Any Permission
|
||||
|
||||
# Openshift messages
|
||||
openshift.scope.user_info=User information
|
||||
openshift.scope.user_check-access=User access information
|
||||
openshift.scope.user_full=Full Access
|
||||
openshift.scope.list-projects=List projects
|
||||
|
||||
error-invalid-value=Invalid value.
|
||||
error-invalid-blank=Please specify value.
|
||||
error-empty=Please specify value.
|
||||
error-invalid-length=Attribute {0} must have a length between {1} and {2}.
|
||||
error-invalid-length-too-short=Attribute {0} must have minimal length of {1}.
|
||||
error-invalid-length-too-long=Attribute {0} must have maximal length of {2}.
|
||||
error-invalid-email=Invalid email address.
|
||||
error-invalid-number=Invalid number.
|
||||
error-number-out-of-range=Attribute {0} must be a number between {1} and {2}.
|
||||
error-number-out-of-range-too-small=Attribute {0} must have minimal value of {1}.
|
||||
error-number-out-of-range-too-big=Attribute {0} must have maximal value of {2}.
|
||||
error-pattern-no-match=Invalid value.
|
||||
error-invalid-uri=Invalid URL.
|
||||
error-invalid-uri-scheme=Invalid URL scheme.
|
||||
error-invalid-uri-fragment=Invalid URL fragment.
|
||||
error-user-attribute-required=Please specify attribute {0}.
|
||||
error-invalid-date=Invalid date.
|
||||
error-user-attribute-read-only=The field {0} is read only.
|
||||
error-username-invalid-character=Username contains invalid character.
|
||||
error-person-name-invalid-character=Name contains invalid character.
|
||||
|
||||
# Signing in page
|
||||
signingIn=Signing in
|
||||
signingInSubMessage=Configure ways to sign in.
|
||||
credentialCreatedAt=Created
|
||||
successRemovedMessage={0} was removed.
|
||||
stopUsingCred=Stop using {0}?
|
||||
changePassword=Change password
|
||||
removeCred=Remove {0}
|
||||
setUpNew=Set up {0}
|
||||
removeCredAriaLabel=Remove credential
|
||||
updateCredAriaLabel=Update credential
|
||||
notSetUp={0} is not set up.
|
||||
two-factor=Two-factor authentication
|
||||
passwordless=Passwordless
|
||||
unknown=Unknown
|
||||
password-display-name=Password
|
||||
password-help-text=Sign in by entering your password.
|
||||
otp-display-name=OTP Codes (2FA)
|
||||
otp-help-text=Enter a verification code from authenticator application.
|
||||
default-help-text=
|
||||
recovery-authn-code=My recovery authentication codes
|
||||
recovery-authn-codes-display-name=Recovery authentication codes
|
||||
recovery-authn-codes-help-text=These codes can be used to regain your access in case your other 2FA means are not available.
|
||||
recovery-codes-number-used={0} recovery codes used
|
||||
recovery-codes-number-remaining={0} recovery codes remaining
|
||||
recovery-codes-generate-new-codes=Generate new codes to ensure access to your account
|
||||
webauthn-display-name=WebAuthn (2FA)
|
||||
webauthn-help-text=Use your security key to sign in.
|
||||
webauthn-passwordless-display-name=Passwordless WebAuthn
|
||||
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
|
||||
basic-authentication=Basic authentication
|
||||
invalidRequestMessage=Invalid request
|
|
@ -0,0 +1,59 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='password' bodyClass='password'; section>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h2>${msg("changePasswordHtmlTitle")}</h2>
|
||||
</div>
|
||||
<div class="col-md-2 subtitle">
|
||||
<span class="subtitle">${msg("allFieldsRequired")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action="${url.passwordUrl}" class="form-horizontal" method="post">
|
||||
<input type="text" id="username" name="username" value="${(account.username!'')}" autocomplete="username" readonly="readonly" style="display:none;">
|
||||
|
||||
<#if password.passwordSet>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password" class="control-label">${msg("password")}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password" name="password" autofocus autocomplete="current-password">
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password-new" class="control-label">${msg("passwordNew")}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password-new" name="password-new" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password-confirm" class="control-label" class="two-lines">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="password" class="form-control" id="password-confirm" name="password-confirm" autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||
<div class="">
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,277 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='authorization' bodyClass='authorization'; section>
|
||||
|
||||
<style>
|
||||
.search-box,.close-icon,.search-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.search-wrapper {
|
||||
width: 500px;
|
||||
margin: auto;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.search-box {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
border: 1px solid #006e9c;
|
||||
outline: 0;
|
||||
border-radius: 15px;
|
||||
background-color: #0085cf;
|
||||
padding: 2px 5px;
|
||||
|
||||
}
|
||||
.search-box:focus {
|
||||
box-shadow: 0 0 15px 5px #b0e0ee;
|
||||
border: 2px solid #bebede;
|
||||
}
|
||||
.close-icon {
|
||||
border:1px solid transparent;
|
||||
background-color: transparent;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.close-icon:after {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background-color: #FA9595;
|
||||
z-index:1;
|
||||
right: 35px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 0 2px #E50F0F;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-box:not(:valid) ~ .close-icon {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function removeScopeElm(elm) {
|
||||
elm.parentNode.removeChild(elm);
|
||||
}
|
||||
|
||||
function removeAllScopes(id) {
|
||||
var scopesElm = document.getElementsByName('removeScope-' + id);
|
||||
|
||||
for (i = 0; i < scopesElm.length; i++) {
|
||||
var td = scopesElm[i].parentNode.parentNode;
|
||||
var tr = td.parentNode;
|
||||
var tbody = tr.parentNode;
|
||||
tbody.removeChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function getChildren(parent, childId) {
|
||||
var childNodes = [];
|
||||
|
||||
for (i = 0; i < parent.childNodes.length; i++) {
|
||||
if (parent.childNodes[i].id == childId) {
|
||||
childNodes.push(parent.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return childNodes;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h2>
|
||||
<a href="${url.resourceUrl}">${msg("myResources")}</a> <i class="fa fa-angle-right"></i> <#if authorization.resource.displayName??>${authorization.resource.displayName}<#else>${authorization.resource.name}</#if>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#if authorization.resource.iconUri??>
|
||||
<img src="${authorization.resource.iconUri}">
|
||||
<br/>
|
||||
</#if>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h3>
|
||||
${msg("peopleAccessResource")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("user")}</th>
|
||||
<th>${msg("permission")}</th>
|
||||
<th>${msg("date")}</th>
|
||||
<th>${msg("action")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if authorization.resource.shares?size != 0>
|
||||
<#list authorization.resource.shares as permission>
|
||||
<form action="${url.getResourceGrant(authorization.resource.id)}" name="revokeForm-${authorization.resource.id}-${permission.requester.username}" method="post">
|
||||
<input type="hidden" name="action" value="revoke">
|
||||
<input type="hidden" name="requester" value="${permission.requester.username}">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<tr>
|
||||
<td>
|
||||
<#if permission.requester.email??>${permission.requester.email}<#else>${permission.requester.username}</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if permission.scopes?size != 0>
|
||||
<#list permission.scopes as scope>
|
||||
<#if scope.granted && scope.scope??>
|
||||
<div class="search-box">
|
||||
<#if scope.scope.displayName??>
|
||||
${scope.scope.displayName}
|
||||
<#else>
|
||||
${scope.scope.name}
|
||||
</#if>
|
||||
<button class="close-icon" type="button" name="removeScope-${authorization.resource.id}-${permission.requester.username}" onclick="removeScopeElm(this.parentNode);document.forms['revokeForm-${authorization.resource.id}-${permission.requester.username}'].submit();"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
<input type="hidden" name="permission_id" value="${scope.id}"/>
|
||||
</div>
|
||||
<#else>
|
||||
${msg("anyPermission")}
|
||||
</#if>
|
||||
</#list>
|
||||
<#else>
|
||||
Any action
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
${permission.createdDate?datetime}
|
||||
</td>
|
||||
<td width="20%" align="middle" style="vertical-align: middle">
|
||||
<a href="#" id="revoke-${authorization.resource.name}-${permission.requester.username}" onclick="removeAllScopes('${authorization.resource.id}-${permission.requester.username}');document.forms['revokeForm-${authorization.resource.id}-${permission.requester.username}'].submit();" type="submit" class="btn btn-primary">${msg("doRevoke")}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="4">${msg("resourceIsNotBeingShared")}</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h3>
|
||||
${msg("resourceManagedPolicies")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("description")}</th>
|
||||
<th>${msg("permission")}</th>
|
||||
<th>${msg("action")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if authorization.resource.policies?size != 0>
|
||||
<#list authorization.resource.policies as permission>
|
||||
<form action="${url.getResourceGrant(authorization.resource.id)}" name="revokePolicyForm-${authorization.resource.id}-${permission.id}" method="post">
|
||||
<input type="hidden" name="action" value="revokePolicy">
|
||||
<input type="hidden" name="permission_id" value="${permission.id}"/>
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<tr>
|
||||
<td>
|
||||
<#if permission.description??>
|
||||
${permission.description}
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if permission.scopes?size != 0>
|
||||
<#list permission.scopes as scope>
|
||||
<div class="search-box">
|
||||
<#if scope.displayName??>
|
||||
${scope.displayName}
|
||||
<#else>
|
||||
${scope.name}
|
||||
</#if>
|
||||
<button class="close-icon" type="button" name="removePolicyScope-${authorization.resource.id}-${permission.id}-${scope.id}" onclick="removeScopeElm(this.parentNode);document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}'].submit();"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
<input type="hidden" name="permission_id" value="${permission.id}:${scope.id}"/>
|
||||
</div>
|
||||
</#list>
|
||||
<#else>
|
||||
${msg("anyAction")}
|
||||
</#if>
|
||||
</td>
|
||||
<td width="20%" align="middle" style="vertical-align: middle">
|
||||
<a href="#" id="revokePolicy-${authorization.resource.name}-${permission.id}" onclick="document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}']['action'].value = 'revokePolicyAll';document.forms['revokePolicyForm-${authorization.resource.id}-${permission.id}'].submit();" type="submit" class="btn btn-primary">${msg("doRevoke")}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
${msg("resourceNoPermissionsGrantingAccess")}
|
||||
</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h3>
|
||||
${msg("shareWithOthers")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form action="${url.getResourceShare(authorization.resource.id)}" name="shareForm" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<label for="password" class="control-label">${msg("username")} or ${msg("email")} </label> <span class="required">*</span>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-8">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<input type="text" class="form-control" id="user_id" name="user_id" autofocus autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<br/>
|
||||
<#list authorization.resource.scopes as scope>
|
||||
<div id="scope" class="search-box">
|
||||
<#if scope.displayName??>
|
||||
${scope.displayName}
|
||||
<#else>
|
||||
${scope.name}
|
||||
</#if>
|
||||
<button class="close-icon" id="share-remove-scope-${authorization.resource.name}-${scope.name}" type="button" onclick="if (getChildren(this.parentNode.parentNode, 'scope').length > 1) {removeScopeElm(this.parentNode)}"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
<input type="hidden" name="scope_id" value="${scope.id}"/>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<br/>
|
||||
<a href="#" onclick="document.forms['shareForm'].submit()" type="submit" id="share-button" class="btn btn-primary">${msg("share")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,403 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='authorization' bodyClass='authorization'; section>
|
||||
<style>
|
||||
.search-box,.close-icon,.search-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.search-wrapper {
|
||||
width: 500px;
|
||||
margin: auto;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.search-box {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
border: 1px solid #006e9c;
|
||||
outline: 0;
|
||||
border-radius: 15px;
|
||||
background-color: #0085cf;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.search-box:focus {
|
||||
box-shadow: 0 0 15px 5px #b0e0ee;
|
||||
border: 2px solid #bebede;
|
||||
}
|
||||
.close-icon {
|
||||
border:1px solid transparent;
|
||||
background-color: transparent;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.close-icon:after {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background-color: #FA9595;
|
||||
z-index:1;
|
||||
right: 35px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 0 2px #E50F0F;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-box:not(:valid) ~ .close-icon {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function showHideActions(elm) {
|
||||
if (elm.style.display == 'none') {
|
||||
elm.style.display = '';
|
||||
} else {
|
||||
elm.style.display = 'none';
|
||||
}
|
||||
}
|
||||
function removeScopeElm(elm) {
|
||||
var td = elm.parentNode;
|
||||
var tr = td.parentNode;
|
||||
var tbody = tr.parentNode;
|
||||
|
||||
td.removeChild(elm);
|
||||
|
||||
var childCount = td.childNodes.length - 1;
|
||||
|
||||
for (i = 0; i < td.childNodes.length; i++) {
|
||||
if (!td.childNodes[i].tagName || td.childNodes[i].tagName.toUpperCase() != 'DIV') {
|
||||
td.removeChild(td.childNodes[i]);
|
||||
childCount--;
|
||||
}
|
||||
}
|
||||
|
||||
if (childCount <= 0) {
|
||||
tbody.removeChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllScopes(id) {
|
||||
var scopesElm = document.getElementsByName('removeScope-' + id);
|
||||
|
||||
for (i = 0; i < scopesElm.length; i++) {
|
||||
var td = scopesElm[i].parentNode.parentNode;
|
||||
var tr = td.parentNode;
|
||||
var tbody = tr.parentNode;
|
||||
tbody.removeChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function selectAllCheckBoxes(formName, elm, name) {
|
||||
var shares = document.forms[formName].getElementsByTagName('input');
|
||||
|
||||
for (i = 0; i < shares.length; i++) {
|
||||
if (shares[i].name == name) {
|
||||
shares[i].checked = elm.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h2>
|
||||
${msg("myResources")}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#if authorization.resourcesWaitingApproval?size != 0>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>
|
||||
${msg("needMyApproval")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("resource")}</th>
|
||||
<th>${msg("requestor")}</th>
|
||||
<th>${msg("permissionRequestion")}</th>
|
||||
<th>${msg("action")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#list authorization.resourcesWaitingApproval as resource>
|
||||
<#list resource.permissions as permission>
|
||||
<form action="${url.getResourceGrant(resource.id)}" name="approveForm-${resource.id}-${permission.requester.username}" method="post">
|
||||
<input type="hidden" name="action" value="grant">
|
||||
<input type="hidden" name="requester" value="${permission.requester.username}">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<tr>
|
||||
<td>
|
||||
<#if resource.displayName??>${resource.displayName}<#else>${resource.name}</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if permission.requester.email??>${permission.requester.email}<#else>${permission.requester.username}</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#list permission.scopes as scope>
|
||||
<#if scope.scope??>
|
||||
<div class="search-box">
|
||||
<#if scope.scope.displayName??>
|
||||
${scope.scope.displayName}
|
||||
<#else>
|
||||
${scope.scope.name}
|
||||
</#if>
|
||||
<button class="close-icon" type="button" id="grant-remove-scope-${resource.name}-${permission.requester.username}-${scope.scope.name}" name="removeScope-${resource.id}-${permission.requester.username}" onclick="removeScopeElm(this.parentNode);document.forms['approveForm-${resource.id}-${permission.requester.username}']['action'].value = 'deny';document.forms['approveForm-${resource.id}-${permission.requester.username}'].submit();"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||
<input type="hidden" name="permission_id" value="${scope.id}"/>
|
||||
</div>
|
||||
<#else>
|
||||
${msg("anyPermission")}
|
||||
</#if>
|
||||
</#list>
|
||||
</td>
|
||||
<td width="20%" align="middle" style="vertical-align: middle">
|
||||
<a href="#" id="grant-${resource.name}-${permission.requester.username}" onclick="document.forms['approveForm-${resource.id}-${permission.requester.username}']['action'].value = 'grant';document.forms['approveForm-${resource.id}-${permission.requester.username}'].submit();" type="submit" class="btn btn-primary">${msg("doApprove")}</a>
|
||||
<a href="#" id="deny-${resource.name}-${permission.requester.username}" onclick="removeAllScopes('${resource.id}-${permission.requester.username}');document.forms['approveForm-${resource.id}-${permission.requester.username}']['action'].value = 'deny';document.forms['approveForm-${resource.id}-${permission.requester.username}'].submit();" type="submit" class="btn btn-danger">${msg("doDeny")}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</#list>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>
|
||||
${msg("myResourcesSub")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("resource")}</th>
|
||||
<th>${msg("application")}</th>
|
||||
<th>${msg("peopleSharingThisResource")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<#if authorization.resources?size != 0>
|
||||
<#list authorization.resources as resource>
|
||||
<tr>
|
||||
<td>
|
||||
<a id="detail-${resource.name}" href="${url.getResourceDetailUrl(resource.id)}">
|
||||
<#if resource.displayName??>${resource.displayName}<#else>${resource.name}</#if>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.resourceServer.baseUri??>
|
||||
<a href="${resource.resourceServer.baseUri}">${resource.resourceServer.name}</a>
|
||||
<#else>
|
||||
${resource.resourceServer.name}
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.shares?size != 0>
|
||||
<a href="${url.getResourceDetailUrl(resource.id)}">${resource.shares?size} <i class="fa fa-users"></i></a>
|
||||
<#else>
|
||||
${msg("notBeingShared")}
|
||||
</#if>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="4">${msg("notHaveAnyResource")}</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>
|
||||
${msg("resourcesSharedWithMe")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form action="${url.resourceUrl}" name="shareForm" method="post">
|
||||
<input type="hidden" name="action" value="cancel"/>
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"><input type="checkbox" onclick="selectAllCheckBoxes('shareForm', this, 'resource_id');" <#if authorization.sharedResources?size == 0>disabled="true"</#if></td>
|
||||
<th>${msg("resource")}</th>
|
||||
<th>${msg("owner")}</th>
|
||||
<th>${msg("application")}</th>
|
||||
<th>${msg("permission")}</th>
|
||||
<th>${msg("date")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if authorization.sharedResources?size != 0>
|
||||
<#list authorization.sharedResources as resource>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="resource_id" value="${resource.id}"/>
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.displayName??>${resource.displayName}<#else>${resource.name}</#if>
|
||||
</td>
|
||||
<td>
|
||||
${resource.ownerName}
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.resourceServer.baseUri??>
|
||||
<a href="${resource.resourceServer.baseUri}">${resource.resourceServer.name}</a>
|
||||
<#else>
|
||||
${resource.resourceServer.name}
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.permissions?size != 0>
|
||||
<ul>
|
||||
<#list resource.permissions as permission>
|
||||
<#list permission.scopes as scope>
|
||||
<#if scope.granted && scope.scope??>
|
||||
<li>
|
||||
<#if scope.scope.displayName??>
|
||||
${scope.scope.displayName}
|
||||
<#else>
|
||||
${scope.scope.name}
|
||||
</#if>
|
||||
</li>
|
||||
<#else>
|
||||
${msg("anyPermission")}
|
||||
</#if>
|
||||
</#list>
|
||||
</#list>
|
||||
</ul>
|
||||
<#else>
|
||||
Any action
|
||||
</#if>
|
||||
</td>
|
||||
<td>
|
||||
${resource.permissions[0].grantedDate?datetime}
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="6">${msg("noResourcesSharedWithYou")}</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<#if authorization.sharedResources?size != 0>
|
||||
<div class="col-md-12">
|
||||
<a href="#" onclick="document.forms['shareForm'].submit();" type="submit" class="btn btn-danger">${msg("doRemoveSharing")}</a>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<#if authorization.resourcesWaitingOthersApproval?size != 0>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>
|
||||
${msg("requestsWaitingApproval")}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<i class="pficon pficon-info"></i> ${msg("havePermissionRequestsWaitingForApproval",authorization.resourcesWaitingOthersApproval?size)}
|
||||
<a href="#" onclick="document.getElementById('waitingApproval').style.display=''">${msg("clickHereForDetails")}</a>
|
||||
<div class="row">
|
||||
<div class="col-md-12"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12"></div>
|
||||
</div>
|
||||
<div class="row" id="waitingApproval" style="display:none">
|
||||
<div class="col-md-12">
|
||||
<form action="${url.resourceUrl}" name="waitingApprovalForm" method="post">
|
||||
<input type="hidden" name="action" value="cancelRequest"/>
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"><input type="checkbox" onclick="selectAllCheckBoxes('waitingApprovalForm', this, 'resource_id');" <#if authorization.resourcesWaitingOthersApproval?size == 0>disabled="true"</#if></th>
|
||||
<th>${msg("resource")}</th>
|
||||
<th>${msg("owner")}</th>
|
||||
<th>${msg("action")}</th>
|
||||
<th>${msg("date")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#list authorization.resourcesWaitingOthersApproval as resource>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="resource_id" value="${resource.id}"/>
|
||||
</td>
|
||||
<td>
|
||||
<#if resource.displayName??>${resource.displayName}<#else>${resource.name}</#if>
|
||||
</td>
|
||||
<td>
|
||||
${resource.ownerName}
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<#list resource.permissions as permission>
|
||||
<#list permission.scopes as scope>
|
||||
<li>
|
||||
<#if scope.scope??>
|
||||
<#if scope.scope.displayName??>
|
||||
${scope.scope.displayName}
|
||||
<#else>
|
||||
${scope.scope.name}
|
||||
</#if>
|
||||
<#else>
|
||||
${msg("anyPermission")}
|
||||
</#if>
|
||||
</li>
|
||||
</#list>
|
||||
</#list>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
${resource.permissions[0].createdDate?datetime}
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<a href="#" onclick="document.forms['waitingApprovalForm'].submit();" type="submit" class="btn btn-danger">${msg("doRemoveRequest")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,218 @@
|
|||
body {
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
background: #080e08;
|
||||
color: #f6f6f6;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #4af626;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 10px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
background: #3a7920;
|
||||
color: #f6f6f6;
|
||||
|
||||
margin: auto auto 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #2cacb0;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #b4778f;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background: #fcfffc;
|
||||
color: #211c1b;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #4c982a;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #1f7b7e;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #7c5263;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: #ffffff;
|
||||
color: #211c1b;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 154, 0, 1) 10%,
|
||||
rgba(208, 222, 33, 1) 20%,
|
||||
rgba(79, 220, 74, 1) 30%,
|
||||
rgba(63, 218, 216, 1) 40%,
|
||||
rgba(47, 201, 226, 1) 50%,
|
||||
rgba(28, 127, 238, 1) 60%,
|
||||
rgba(95, 21, 242, 1) 70%,
|
||||
rgba(186, 12, 248, 1) 80%,
|
||||
rgba(251, 7, 217, 1) 90%,
|
||||
rgba(255, 0, 0, 1) 100%
|
||||
);
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#header-content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
#header-content h1 {
|
||||
color: black;
|
||||
margin: auto auto auto 1rem;
|
||||
}
|
||||
|
||||
#header-content img {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
span.copyleft {
|
||||
display: inline-block;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
footer a:link, footer a:visited {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
background: #332c29;
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: #332c29;
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
textarea.big {
|
||||
width: 100%;
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
.error, .required, .alert-error, .form-group.has-error {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
#nav {
|
||||
margin: auto 0 auto auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#nav a {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#header-content a:link {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
#header-content a:visited {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#header-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
label.field-with-error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
img.stats {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
padding: 5px;
|
||||
}
|
||||
th {
|
||||
border-bottom: solid 2px currentColor;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
border-right: solid 2px currentColor;
|
||||
border-left: none;
|
||||
}
|
||||
td {
|
||||
border-right: solid 2px currentColor;
|
||||
border-left: solid 2px currentColor;
|
||||
}
|
||||
td:last-child {
|
||||
border-right: none;
|
||||
border-left: solid 2px currentColor;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
.responsive-wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
|
@ -0,0 +1,114 @@
|
|||
// for embedded scripts, quoted and modified from https://github.com/swansontec/rfc4648.js by William Swanson
|
||||
'use strict';
|
||||
var base64url = base64url || {};
|
||||
(function(base64url) {
|
||||
|
||||
function parse (string, encoding, opts = {}) {
|
||||
// Build the character lookup table:
|
||||
if (!encoding.codes) {
|
||||
encoding.codes = {};
|
||||
for (let i = 0; i < encoding.chars.length; ++i) {
|
||||
encoding.codes[encoding.chars[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// The string must have a whole number of bytes:
|
||||
if (!opts.loose && (string.length * encoding.bits) & 7) {
|
||||
throw new SyntaxError('Invalid padding');
|
||||
}
|
||||
|
||||
// Count the padding bytes:
|
||||
let end = string.length;
|
||||
while (string[end - 1] === '=') {
|
||||
--end;
|
||||
|
||||
// If we get a whole number of bytes, there is too much padding:
|
||||
if (!opts.loose && !(((string.length - end) * encoding.bits) & 7)) {
|
||||
throw new SyntaxError('Invalid padding');
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the output:
|
||||
const out = new (opts.out || Uint8Array)(((end * encoding.bits) / 8) | 0);
|
||||
|
||||
// Parse the data:
|
||||
let bits = 0; // Number of bits currently in the buffer
|
||||
let buffer = 0; // Bits waiting to be written out, MSB first
|
||||
let written = 0; // Next byte to write
|
||||
for (let i = 0; i < end; ++i) {
|
||||
// Read one character from the string:
|
||||
const value = encoding.codes[string[i]];
|
||||
if (value === void 0) {
|
||||
throw new SyntaxError('Invalid character ' + string[i]);
|
||||
}
|
||||
|
||||
// Append the bits to the buffer:
|
||||
buffer = (buffer << encoding.bits) | value;
|
||||
bits += encoding.bits;
|
||||
|
||||
// Write out some bits if the buffer has a byte's worth:
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out[written++] = 0xff & (buffer >> bits);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we have received just enough bits:
|
||||
if (bits >= encoding.bits || 0xff & (buffer << (8 - bits))) {
|
||||
throw new SyntaxError('Unexpected end of data');
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
function stringify (data, encoding, opts = {}) {
|
||||
const { pad = true } = opts;
|
||||
const mask = (1 << encoding.bits) - 1;
|
||||
let out = '';
|
||||
|
||||
let bits = 0; // Number of bits currently in the buffer
|
||||
let buffer = 0; // Bits waiting to be written out, MSB first
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
// Slurp data into the buffer:
|
||||
buffer = (buffer << 8) | (0xff & data[i]);
|
||||
bits += 8;
|
||||
|
||||
// Write out as much as we can:
|
||||
while (bits > encoding.bits) {
|
||||
bits -= encoding.bits;
|
||||
out += encoding.chars[mask & (buffer >> bits)];
|
||||
}
|
||||
}
|
||||
|
||||
// Partial character:
|
||||
if (bits) {
|
||||
out += encoding.chars[mask & (buffer << (encoding.bits - bits))];
|
||||
}
|
||||
|
||||
// Add padding characters until we hit a byte boundary:
|
||||
if (pad) {
|
||||
while ((out.length * encoding.bits) & 7) {
|
||||
out += '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
const encoding = {
|
||||
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
|
||||
bits: 6
|
||||
}
|
||||
|
||||
base64url.decode = function (string, opts) {
|
||||
return parse(string, encoding, opts);
|
||||
}
|
||||
|
||||
base64url.encode = function (data, opts) {
|
||||
return stringify(data, encoding, opts)
|
||||
}
|
||||
|
||||
return base64url;
|
||||
}(base64url));
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.mainLayout active='sessions' bodyClass='sessions'; section>
|
||||
<h2>${msg("sessionsHtmlTitle")}</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${msg("ip")}</th>
|
||||
<th>${msg("started")}</th>
|
||||
<th>${msg("lastAccess")}</th>
|
||||
<th>${msg("expires")}</th>
|
||||
<th>${msg("clients")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<#list sessions.sessions as session>
|
||||
<tr>
|
||||
<td>${session.ipAddress}</td>
|
||||
<td>${session.started?datetime}</td>
|
||||
<td>${session.lastAccess?datetime}</td>
|
||||
<td>${session.expires?datetime}</td>
|
||||
<td>
|
||||
<#list session.clients as client>
|
||||
${client}<br/>
|
||||
</#list>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<form action="${url.sessionsUrl}" method="post">
|
||||
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||
<input id="logout-all-sessions" class="btn btn-default"
|
||||
type="submit" value="${msg("doLogOutAllSessions")}"/>
|
||||
</form>
|
||||
</@layout.mainLayout>
|
|
@ -0,0 +1,68 @@
|
|||
<#macro mainLayout active bodyClass>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<title>${msg("accountManagementTitle")}</title>
|
||||
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico">
|
||||
<#if properties.stylesCommon?has_content>
|
||||
<#list properties.stylesCommon?split(' ') as style>
|
||||
<link href="${url.resourcesCommonPath}/${style}" rel="stylesheet"/>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if properties.styles?has_content>
|
||||
<#list properties.styles?split(' ') as style>
|
||||
<link href="${url.resourcesPath}/${style}" rel="stylesheet"/>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if properties.scripts?has_content>
|
||||
<#list properties.scripts?split(' ') as script>
|
||||
<script type="text/javascript" src="${url.resourcesPath}/${script}"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
</head>
|
||||
<body class="admin-console user ${bodyClass}">
|
||||
<header>
|
||||
<div id="header-content">
|
||||
<img src="${url.resourcesPath}/img/favicon.ico" width="32" height="32" alt="UNIX.dog"/>
|
||||
<h1>UNIX.dog Account</h1>
|
||||
<div id="nav">
|
||||
<a href="${url.accountUrl}">${msg("account")}</a>
|
||||
<a href="${url.authenticationUrl}">${msg("authentication")}</a>
|
||||
<a href="${url.sessionsUrl}">${msg("sessions")}</a>
|
||||
<a href="${url.applicationsUrl}">${msg("applications")}</a>
|
||||
<#if features.log><a href="${url.logUrl}">${msg("log")}</a></#if>
|
||||
<#if realm.userManagedAccessAllowed && features.authorization><a
|
||||
href="${url.resourceUrl}">${msg("myResources")}</a></#if>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<#if message?has_content>
|
||||
<section class="alert alert-${message.type}">
|
||||
<p>
|
||||
${kcSanitize(message.summary)?no_esc}
|
||||
</p>
|
||||
</section>
|
||||
</#if>
|
||||
<section class="content">
|
||||
<#nested "content">
|
||||
</section>
|
||||
</main>
|
||||
<footer>
|
||||
🄯 <a href="https://git.unix.dog/UNIX.dog/website">Copyleft</a> UNIX.dog, 2022. All pages licensed under
|
||||
CC BY-NC-SA 4.0.
|
||||
<br>
|
||||
Please follow <a href="/rules">all rules</a> while using these services.
|
||||
<br>
|
||||
<a href="${url.getLogoutUrl()}">${msg("doSignOut")}.</a>
|
||||
<#if referrer?has_content && referrer.url?has_content><a
|
||||
href="${referrer.url}" id="referrer">${msg("backTo",referrer.name)}.</a></#if>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</#macro>
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
styles=css/main.css
|
||||
kcInputErrorMessageClass=error
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
_ __ _ _
|
||||
| |/ /___ _ _ ___| | ___ __ _| | __
|
||||
| ' // _ \ | | |/ __| |/ _ \ / _` | |/ /
|
||||
| . \ __/ |_| | (__| | (_) | (_| | <
|
||||
|_|\_\___|\__, |\___|_|\___/ \__,_|_|\_\
|
||||
|___/
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
<#if code.success>
|
||||
${msg("codeSuccessTitle")}
|
||||
<#else>
|
||||
${kcSanitize(msg("codeErrorTitle", code.error))}
|
||||
</#if>
|
||||
<#elseif section = "form">
|
||||
<div id="kc-code">
|
||||
<#if code.success>
|
||||
<p>${msg("copyCodeInstruction")}</p>
|
||||
<input id="code" class="${properties.kcTextareaClass!}" value="${code.code}"/>
|
||||
<#else>
|
||||
<p id="error">${kcSanitize(code.error)}</p>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,33 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
|
||||
<#if section = "header">
|
||||
${msg("deleteAccountConfirm")}
|
||||
|
||||
<#elseif section = "form">
|
||||
|
||||
<form action="${url.loginAction}" class="form-vertical" method="post">
|
||||
|
||||
<div class="alert alert-warning" style="margin-top:0 !important;margin-bottom:30px !important">
|
||||
<span class="pficon pficon-warning-triangle-o"></span>
|
||||
${msg("irreversibleAction")}
|
||||
</div>
|
||||
|
||||
<p>${msg("deletingImplies")}</p>
|
||||
<ul style="color: #72767b;list-style: disc;list-style-position: inside;">
|
||||
<li>${msg("loggingOutImmediately")}</li>
|
||||
<li>${msg("errasingData")}</li>
|
||||
</ul>
|
||||
|
||||
<p class="delete-account-text">${msg("finalDeletionConfirmation")}</p>
|
||||
|
||||
<div id="kc-form-buttons">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doConfirmDelete")}" />
|
||||
<#if triggered_from_aia>
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" style="margin-left: calc(100% - 220px)" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
</#if>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<#if section = "header">
|
||||
${kcSanitize(msg("errorTitle"))?no_esc}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-error-message">
|
||||
<p class="instruction">${kcSanitize(message.summary)?no_esc}</p>
|
||||
<#if skipLink??>
|
||||
<#else>
|
||||
<#if client?? && client.baseUrl?has_content>
|
||||
<p><a id="backToApplication" href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
|
||||
</#if>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,30 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
<script>
|
||||
document.title = "${msg("frontchannel-logout.title")}";
|
||||
</script>
|
||||
${msg("frontchannel-logout.title")}
|
||||
<#elseif section = "form">
|
||||
<p>${msg("frontchannel-logout.message")}</p>
|
||||
<ul>
|
||||
<#list logout.clients as client>
|
||||
<li>
|
||||
${client.name}
|
||||
<iframe src="${client.frontChannelLogoutUrl}" style="display:none;"></iframe>
|
||||
</li>
|
||||
</#list>
|
||||
</ul>
|
||||
<#if logout.logoutRedirectUri?has_content>
|
||||
<script>
|
||||
function readystatechange(event) {
|
||||
if (document.readyState=='complete') {
|
||||
window.location.replace('${logout.logoutRedirectUri}');
|
||||
}
|
||||
}
|
||||
document.addEventListener('readystatechange', readystatechange);
|
||||
</script>
|
||||
<a id="continue" class="btn btn-primary" href="${logout.logoutRedirectUri}">${msg("doContinue")}</a>
|
||||
</#if>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,23 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("loginIdpReviewProfileTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-idp-review-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields/>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,24 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<#if section = "header">
|
||||
<#if messageHeader??>
|
||||
${messageHeader}
|
||||
<#else>
|
||||
${message.summary}
|
||||
</#if>
|
||||
<#elseif section = "form">
|
||||
<div id="kc-info-message">
|
||||
<p class="instruction">${message.summary}<#if requiredActions??><#list requiredActions>: <b><#items as reqActionItem>${kcSanitize(msg("requiredAction.${reqActionItem}"))?no_esc}<#sep>, </#items></b></#list><#else></#if></p>
|
||||
<#if skipLink??>
|
||||
<#else>
|
||||
<#if pageRedirectUri?has_content>
|
||||
<p><a href="${pageRedirectUri}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
|
||||
<#elseif actionUri?has_content>
|
||||
<p><a href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
|
||||
<#elseif (client.baseUrl)?has_content>
|
||||
<p><a href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
|
||||
</#if>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,108 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayRequiredFields=false displayMessage=!messagesPerField.existsError('totp','userLabel'); section>
|
||||
|
||||
<#if section = "header">
|
||||
${msg("loginTotpTitle")}
|
||||
<#elseif section = "form">
|
||||
<ol id="kc-totp-settings">
|
||||
<li>
|
||||
<p>${msg("loginTotpStep1")}</p>
|
||||
|
||||
<ul id="kc-totp-supported-apps">
|
||||
<#list totp.supportedApplications as app>
|
||||
<li>${msg(app)}</li>
|
||||
</#list>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<#if mode?? && mode = "manual">
|
||||
<li>
|
||||
<p>${msg("loginTotpManualStep2")}</p>
|
||||
<p><span id="kc-totp-secret-key">${totp.totpSecretEncoded}</span></p>
|
||||
<p><a href="${totp.qrUrl}" id="mode-barcode">${msg("loginTotpScanBarcode")}</a></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>${msg("loginTotpManualStep3")}</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li id="kc-totp-type">${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}</li>
|
||||
<li id="kc-totp-algorithm">${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li>
|
||||
<li id="kc-totp-digits">${msg("loginTotpDigits")}: ${totp.policy.digits}</li>
|
||||
<#if totp.policy.type = "totp">
|
||||
<li id="kc-totp-period">${msg("loginTotpInterval")}: ${totp.policy.period}</li>
|
||||
<#elseif totp.policy.type = "hotp">
|
||||
<li id="kc-totp-counter">${msg("loginTotpCounter")}: ${totp.policy.initialCounter}</li>
|
||||
</#if>
|
||||
</ul>
|
||||
</p>
|
||||
</li>
|
||||
<#else>
|
||||
<li>
|
||||
<p>${msg("loginTotpStep2")}</p>
|
||||
<img id="kc-totp-secret-qr-code" src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
|
||||
<p><a href="${totp.manualUrl}" id="mode-manual">${msg("loginTotpUnableToScan")}</a></p>
|
||||
</li>
|
||||
</#if>
|
||||
<li>
|
||||
<p>${msg("loginTotpStep3")}</p>
|
||||
<p>${msg("loginTotpStep3DeviceName")}</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-totp-settings-form" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<label for="totp" class="control-label">${msg("authenticatorCode")}</label> <span class="required">*</span>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="totp" name="totp" autocomplete="off" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('totp')>
|
||||
<span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('totp'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
|
||||
</div>
|
||||
<input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
|
||||
<#if mode??><input type="hidden" id="mode" name="mode" value="${mode}"/></#if>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<label for="userLabel" class="control-label">${msg("loginTotpDeviceName")}</label> <#if totp.otpCredentials?size gte 1><span class="required">*</span></#if>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="userLabel" name="userLabel" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('userLabel')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('userLabel')>
|
||||
<span id="input-error-otp-label" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('userLabel'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#if isAppInitiatedAction??>
|
||||
<input type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="saveTOTPBtn" value="${msg("doSubmit")}"
|
||||
/>
|
||||
<button type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="cancelTOTPBtn" name="cancel-aia" value="true" />${msg("doCancel")}
|
||||
</button>
|
||||
<#else>
|
||||
<input type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="saveTOTPBtn" value="${msg("doSubmit")}"
|
||||
/>
|
||||
</#if>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,13 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("confirmLinkIdpTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="updateProfile" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="linkAccount" value="linkAccount">${msg("confirmLinkIdpContinue", idpDisplayName)}</button>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("emailLinkIdpTitle", idpDisplayName)}
|
||||
<#elseif section = "form">
|
||||
<p id="instruction1" class="instruction">
|
||||
${msg("emailLinkIdp1", idpDisplayName, brokerContext.username, realm.displayName)}
|
||||
</p>
|
||||
<p id="instruction2" class="instruction">
|
||||
${msg("emailLinkIdp2")} <a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
|
||||
</p>
|
||||
<p id="instruction3" class="instruction">
|
||||
${msg("emailLinkIdp4")} <a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailLinkIdp5")}
|
||||
</p>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,68 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout bodyClass="oauth"; section>
|
||||
<#if section = "header">
|
||||
<#if client.attributes.logoUri??>
|
||||
<img src="${client.attributes.logoUri}"/>
|
||||
</#if>
|
||||
<p>
|
||||
<#if client.name?has_content>
|
||||
${msg("oauthGrantTitle",advancedMsg(client.name))}
|
||||
<#else>
|
||||
${msg("oauthGrantTitle",client.clientId)}
|
||||
</#if>
|
||||
</p>
|
||||
<#elseif section = "form">
|
||||
<div id="kc-oauth" class="content-area">
|
||||
<h3>${msg("oauthGrantRequest")}</h3>
|
||||
<ul>
|
||||
<#if oauth.clientScopesRequested??>
|
||||
<#list oauth.clientScopesRequested as clientScope>
|
||||
<li>
|
||||
<span><#if !clientScope.dynamicScopeParameter??>
|
||||
${advancedMsg(clientScope.consentScreenText)}
|
||||
<#else>
|
||||
${advancedMsg(clientScope.consentScreenText)}: <b>${clientScope.dynamicScopeParameter}</b>
|
||||
</#if>
|
||||
</span>
|
||||
</li>
|
||||
</#list>
|
||||
</#if>
|
||||
</ul>
|
||||
<#if client.attributes.policyUri?? || client.attributes.tosUri??>
|
||||
<h3>
|
||||
<#if client.name?has_content>
|
||||
${msg("oauthGrantInformation",advancedMsg(client.name))}
|
||||
<#else>
|
||||
${msg("oauthGrantInformation",client.clientId)}
|
||||
</#if>
|
||||
<#if client.attributes.tosUri??>
|
||||
${msg("oauthGrantReview")}
|
||||
<a href="${client.attributes.tosUri}" target="_blank">${msg("oauthGrantTos")}</a>
|
||||
</#if>
|
||||
<#if client.attributes.policyUri??>
|
||||
${msg("oauthGrantReview")}
|
||||
<a href="${client.attributes.policyUri}" target="_blank">${msg("oauthGrantPolicy")}</a>
|
||||
</#if>
|
||||
</h3>
|
||||
</#if>
|
||||
|
||||
<form class="form-actions" action="${url.oauthAction}" method="POST">
|
||||
<input type="hidden" name="code" value="${oauth.code}">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="accept" id="kc-login" type="submit" value="${msg("doYes")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doNo")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,31 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("oauth2DeviceVerificationTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-user-verify-device-user-code-form" class="${properties.kcFormClass!}" action="${url.oauth2DeviceVerificationAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="device-user-code" class="${properties.kcLabelClass!}">${msg("verifyOAuth2DeviceUserCode")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="device-user-code" name="device_user_code" autocomplete="off" type="text" class="${properties.kcInputClass!}" autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,58 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section>
|
||||
<#if section="header">
|
||||
${msg("doLogIn")}
|
||||
<#elseif section="form">
|
||||
<form id="kc-otp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}"
|
||||
method="post">
|
||||
<#if otpLogin.userOtpCredentials?size gt 1>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<#list otpLogin.userOtpCredentials as otpCredential>
|
||||
<input id="kc-otp-credential-${otpCredential?index}" class="${properties.kcLoginOTPListInputClass!}" type="radio" name="selectedCredentialId" value="${otpCredential.id}" <#if otpCredential.id == otpLogin.selectedCredentialId>checked="checked"</#if>>
|
||||
<label for="kc-otp-credential-${otpCredential?index}" class="${properties.kcLoginOTPListClass!}" tabindex="${otpCredential?index}">
|
||||
<span class="${properties.kcLoginOTPListItemHeaderClass!}">
|
||||
<span class="${properties.kcLoginOTPListItemIconBodyClass!}">
|
||||
<i class="${properties.kcLoginOTPListItemIconClass!}" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="${properties.kcLoginOTPListItemTitleClass!}">${otpCredential.userLabel}</span>
|
||||
</span>
|
||||
</label>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="otp" class="${properties.kcLabelClass!}">${msg("loginOtpOneTime")}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="otp" name="otp" autocomplete="off" type="text" class="${properties.kcInputClass!}"
|
||||
autofocus aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"/>
|
||||
|
||||
<#if messagesPerField.existsError('totp')>
|
||||
<span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}"
|
||||
aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('totp'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="login" id="kc-login" type="submit" value="${msg("doLogIn")}" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,11 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("pageExpiredTitle")}
|
||||
<#elseif section = "form">
|
||||
<p id="instruction1" class="instruction">
|
||||
${msg("pageExpiredMsg1")} <a id="loginRestartLink" href="${url.loginRestartFlowUrl}">${msg("doClickHere")}</a> .<br/>
|
||||
${msg("pageExpiredMsg2")} <a id="loginContinueLink" href="${url.loginAction}">${msg("doClickHere")}</a> .
|
||||
</p>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,32 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password'); section>
|
||||
<#if section = "header">
|
||||
${msg("doLogIn")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-form">
|
||||
<div id="kc-form-wrapper">
|
||||
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}"
|
||||
method="post">
|
||||
<div class="${properties.kcFormGroupClass!} no-bottom-margin">
|
||||
<label for="password"
|
||||
class="${properties.kcLabelClass!}">${msg("password")}:</label>
|
||||
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
type="password" autocomplete="on" autofocus
|
||||
aria-invalid="<#if messagesPerField.existsError('password')>true</#if>"
|
||||
/>
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
|
||||
<input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,184 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
|
||||
<#if section = "header">
|
||||
${msg("recovery-code-config-header")}
|
||||
<#elseif section = "form">
|
||||
<!-- warning -->
|
||||
<div class="pf-c-alert pf-m-warning pf-m-inline ${properties.kcRecoveryCodesWarning}" aria-label="Warning alert">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="pficon-warning-triangle-o" aria-hidden="true"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
<span class="pf-screen-reader">Warning alert:</span>
|
||||
${msg("recovery-code-config-warning-title")}
|
||||
</h4>
|
||||
<div class="pf-c-alert__description">
|
||||
<p>${msg("recovery-code-config-warning-message")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol id="kc-recovery-codes-list" class="${properties.kcRecoveryCodesList!}">
|
||||
<#list recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList as code>
|
||||
<li><span>${code?counter}:</span> ${code[0..3]}-${code[4..7]}-${code[8..]}</li>
|
||||
</#list>
|
||||
</ol>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="${properties.kcRecoveryCodesActions}">
|
||||
<button id="printRecoveryCodes" class="pf-c-button pf-m-link" type="button">
|
||||
<i class="pficon-print"></i> ${msg("recovery-codes-print")}
|
||||
</button>
|
||||
<button id="downloadRecoveryCodes" class="pf-c-button pf-m-link" type="button">
|
||||
<i class="pficon-save"></i> ${msg("recovery-codes-download")}
|
||||
</button>
|
||||
<button id="copyRecoveryCodes" class="pf-c-button pf-m-link" type="button">
|
||||
<i class="pficon-blueprint"></i> ${msg("recovery-codes-copy")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- confirmation checkbox -->
|
||||
<div class="${properties.kcCheckClass} ${properties.kcRecoveryCodesConfirmation}">
|
||||
<input class="${properties.kcCheckInputClass}" type="checkbox" id="kcRecoveryCodesConfirmationCheck" name="kcRecoveryCodesConfirmationCheck"
|
||||
onchange="document.getElementById('saveRecoveryAuthnCodesBtn').disabled = !this.checked;"
|
||||
/>
|
||||
<label class="${properties.kcCheckLabelClass}" for="kcRecoveryCodesConfirmationCheck">${msg("recovery-codes-confirmation-message")}</label>
|
||||
</div>
|
||||
|
||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-recovery-codes-settings-form" method="post">
|
||||
<input type="hidden" name="generatedRecoveryAuthnCodes" value="${recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}" />
|
||||
<input type="hidden" name="generatedAt" value="${recoveryAuthnCodesConfigBean.generatedAt?c}" />
|
||||
<input type="hidden" id="userLabel" name="userLabel" value="${msg("recovery-codes-label-default")}" />
|
||||
|
||||
<#if isAppInitiatedAction??>
|
||||
<input type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="saveRecoveryAuthnCodesBtn" value="${msg("recovery-codes-action-complete")}"
|
||||
disabled
|
||||
/>
|
||||
<button type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="cancelRecoveryAuthnCodesBtn" name="cancel-aia" value="true" />${msg("recovery-codes-action-cancel")}
|
||||
</button>
|
||||
<#else>
|
||||
<input type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="saveRecoveryAuthnCodesBtn" value="${msg("recovery-codes-action-complete")}"
|
||||
disabled
|
||||
/>
|
||||
</#if>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
/* copy recovery codes */
|
||||
function copyRecoveryCodes() {
|
||||
var tmpTextarea = document.createElement("textarea");
|
||||
var codes = document.getElementById("kc-recovery-codes-list").getElementsByTagName("li");
|
||||
for (i = 0; i < codes.length; i++) {
|
||||
tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\n";
|
||||
}
|
||||
document.body.appendChild(tmpTextarea);
|
||||
tmpTextarea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(tmpTextarea);
|
||||
}
|
||||
|
||||
var copyButton = document.getElementById("copyRecoveryCodes");
|
||||
copyButton && copyButton.addEventListener("click", function () {
|
||||
copyRecoveryCodes();
|
||||
});
|
||||
|
||||
/* download recovery codes */
|
||||
function formatCurrentDateTime() {
|
||||
var dt = new Date();
|
||||
var options = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
|
||||
return dt.toLocaleString('en-US', options);
|
||||
}
|
||||
|
||||
function parseRecoveryCodeList() {
|
||||
var recoveryCodes = document.querySelectorAll(".kc-recovery-codes-list li");
|
||||
var recoveryCodeList = "";
|
||||
|
||||
for (var i = 0; i < recoveryCodes.length; i++) {
|
||||
var recoveryCodeLiElement = recoveryCodes[i].innerText;
|
||||
recoveryCodeList += recoveryCodeLiElement + "\r\n";
|
||||
}
|
||||
|
||||
return recoveryCodeList;
|
||||
}
|
||||
|
||||
function buildDownloadContent() {
|
||||
var recoveryCodeList = parseRecoveryCodeList();
|
||||
var dt = new Date();
|
||||
var options = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
|
||||
return fileBodyContent =
|
||||
"${msg("recovery-codes-download-file-header")}\n\n" +
|
||||
recoveryCodeList + "\n" +
|
||||
"${msg("recovery-codes-download-file-description")}\n\n" +
|
||||
"${msg("recovery-codes-download-file-date")} " + formatCurrentDateTime();
|
||||
}
|
||||
|
||||
function setUpDownloadLinkAndDownload(filename, text) {
|
||||
var el = document.createElement('a');
|
||||
el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
el.setAttribute('download', filename);
|
||||
el.style.display = 'none';
|
||||
document.body.appendChild(el);
|
||||
el.click();
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
|
||||
function downloadRecoveryCodes() {
|
||||
setUpDownloadLinkAndDownload('kc-download-recovery-codes.txt', buildDownloadContent());
|
||||
}
|
||||
|
||||
var downloadButton = document.getElementById("downloadRecoveryCodes");
|
||||
downloadButton && downloadButton.addEventListener("click", downloadRecoveryCodes);
|
||||
|
||||
/* print recovery codes */
|
||||
function buildPrintContent() {
|
||||
var recoveryCodeListHTML = document.getElementById('kc-recovery-codes-list').innerHTML;
|
||||
var styles =
|
||||
`@page { size: auto; margin-top: 0; }
|
||||
body { width: 480px; }
|
||||
div { list-style-type: none; font-family: monospace }
|
||||
p:first-of-type { margin-top: 48px }`
|
||||
|
||||
return printFileContent =
|
||||
"<html><style>" + styles + "</style><body>" +
|
||||
"<title>kc-download-recovery-codes</title>" +
|
||||
"<p>${msg("recovery-codes-download-file-header")}</p>" +
|
||||
"<div>" + recoveryCodeListHTML + "</div>" +
|
||||
"<p>${msg("recovery-codes-download-file-description")}</p>" +
|
||||
"<p>${msg("recovery-codes-download-file-date")} " + formatCurrentDateTime() + "</p>" +
|
||||
"</body></html>";
|
||||
}
|
||||
|
||||
function printRecoveryCodes() {
|
||||
var w = window.open();
|
||||
w.document.write(buildPrintContent());
|
||||
w.print();
|
||||
w.close();
|
||||
}
|
||||
|
||||
var printButton = document.getElementById("printRecoveryCodes");
|
||||
printButton && printButton.addEventListener("click", printRecoveryCodes);
|
||||
</script>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,44 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('recoveryCodeInput'); section>
|
||||
|
||||
<#if section = "header">
|
||||
${msg("auth-recovery-code-header")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-recovery-code-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="recoveryCodeInput" class="${properties.kcLabelClass!}">${msg("auth-recovery-code-prompt", recoveryAuthnCodesInputBean.codeNumber?c)}</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input tabindex="1" id="recoveryCodeInput"
|
||||
name="recoveryCodeInput"
|
||||
aria-invalid="<#if messagesPerField.existsError('recoveryCodeInput')>true</#if>"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="${properties.kcInputClass!}"
|
||||
autofocus/>
|
||||
|
||||
<#if messagesPerField.existsError('recoveryCodeInput')>
|
||||
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('recoveryCodeInput'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="login" id="kc-login" type="submit" value="${msg("doLogIn")}" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,39 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section>
|
||||
<#if section = "header">
|
||||
${msg("emailForgotTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus value="${(auth.attemptedUsername!'')}" aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"/>
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<#elseif section = "info" >
|
||||
<#if realm.duplicateEmailsAllowed>
|
||||
${msg("emailInstructionUsername")}
|
||||
<#else>
|
||||
${msg("emailInstruction")}
|
||||
</#if>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,71 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section>
|
||||
<#if section = "header">
|
||||
${msg("updatePasswordTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<input type="text" id="username" name="username" value="${username}" autocomplete="username"
|
||||
readonly="readonly" style="display:none;"/>
|
||||
<input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}"
|
||||
autofocus autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-confirm" name="password-confirm"
|
||||
class="${properties.kcInputClass!}"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password-confirm')>
|
||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked> ${msg("logoutOtherSessions")}</label>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,99 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section>
|
||||
<#if section = "header">
|
||||
${msg("loginProfileTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<#if user.editUsernameAllowed>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" name="username" value="${(user.username!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<#if user.editEmailAllowed>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" name="email" value="${(user.email!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('email')>
|
||||
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('email'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" name="firstName" value="${(user.firstName!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('firstName')>
|
||||
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" name="lastName" value="${(user.lastName!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('lastName')>
|
||||
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,89 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username') displayInfo=(realm.password && realm.registrationAllowed && !registrationDisabled??); section>
|
||||
<#if section = "header">
|
||||
${msg("loginAccountTitle")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-form">
|
||||
<div id="kc-form-wrapper">
|
||||
<#if realm.password>
|
||||
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}"
|
||||
method="post">
|
||||
<#if !usernameHidden??>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="username"
|
||||
class="${properties.kcLabelClass!}"><#if
|
||||
!realm.loginWithEmailAllowed>${msg("username")}<#elseif
|
||||
!realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>:</label>
|
||||
|
||||
<input tabindex="1" id="username"
|
||||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
class="${properties.kcInputClass!}" name="username"
|
||||
value="${(login.username!'')}"
|
||||
type="text" autofocus autocomplete="off"/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
|
||||
<div id="kc-form-options">
|
||||
<#if realm.rememberMe && !usernameHidden??>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<#if login.rememberMe??>
|
||||
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"
|
||||
checked> ${msg("rememberMe")}
|
||||
<#else>
|
||||
<input tabindex="3" id="rememberMe" name="rememberMe"
|
||||
type="checkbox"> ${msg("rememberMe")}
|
||||
</#if>
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
|
||||
<input tabindex="4"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#elseif section = "info" >
|
||||
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
|
||||
<div id="kc-registration">
|
||||
<span>${msg("noAccount")} <a tabindex="6" href="${url.registrationUrl}">${msg("doRegister")}</a></span>
|
||||
</div>
|
||||
</#if>
|
||||
<#elseif section = "socialProviders" >
|
||||
<#if realm.password && social.providers??>
|
||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||
<hr/>
|
||||
<h4>${msg("identity-provider-login-label")}</h4>
|
||||
|
||||
<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountListGridClass!}</#if>">
|
||||
<#list social.providers as p>
|
||||
<a id="social-${p.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
|
||||
type="button" href="${p.loginUrl}">
|
||||
<#if p.iconClasses?has_content>
|
||||
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
|
||||
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
|
||||
<#else>
|
||||
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
|
||||
</#if>
|
||||
</a>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,14 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=true; section>
|
||||
<#if section = "header">
|
||||
${msg("emailVerifyTitle")}
|
||||
<#elseif section = "form">
|
||||
<p class="instruction">${msg("emailVerifyInstruction1",user.email)}</p>
|
||||
<#elseif section = "info">
|
||||
<p class="instruction">
|
||||
${msg("emailVerifyInstruction2")}
|
||||
<br/>
|
||||
<a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction3")}
|
||||
</p>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,55 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("doLogIn")}
|
||||
<#elseif section = "form">
|
||||
|
||||
<form id="kc-x509-login-info" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="certificate_subjectDN" class="${properties.kcLabelClass!}">${msg("clientCertificate")}</label>
|
||||
</div>
|
||||
<#if x509.formData.subjectDN??>
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label id="certificate_subjectDN" class="${properties.kcLabelClass!}">${(x509.formData.subjectDN!"")}</label>
|
||||
</div>
|
||||
<#else>
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label id="certificate_subjectDN" class="${properties.kcLabelClass!}">${msg("noCertificate")}</label>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
|
||||
<#if x509.formData.isUserEnabled??>
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("doX509Login")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label id="username" class="${properties.kcLabelClass!}">${(x509.formData.username!'')}</label>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doContinue")}"/>
|
||||
<#if x509.formData.isUserEnabled??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doIgnore")}"/>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,105 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
|
||||
<#if section = "header">
|
||||
${msg("loginAccountTitle")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-form">
|
||||
<div id="kc-form-wrapper">
|
||||
<#if realm.password>
|
||||
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
|
||||
<#if !usernameHidden??>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
|
||||
<input tabindex="1" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username','password')>
|
||||
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
|
||||
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if usernameHidden?? && messagesPerField.existsError('username','password')>
|
||||
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
|
||||
<div id="kc-form-options">
|
||||
<#if realm.rememberMe && !usernameHidden??>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<#if login.rememberMe??>
|
||||
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
|
||||
<#else>
|
||||
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
|
||||
</#if>
|
||||
</label>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<#if realm.resetPasswordAllowed>
|
||||
<span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
|
||||
<input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<#elseif section = "info" >
|
||||
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
|
||||
<div id="kc-registration-container">
|
||||
<div id="kc-registration">
|
||||
<span>${msg("noAccount")} <a tabindex="6"
|
||||
href="${url.registrationUrl}">${msg("doRegister")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<#elseif section = "socialProviders" >
|
||||
<#if realm.password && social.providers??>
|
||||
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
|
||||
<hr/>
|
||||
<h4>${msg("identity-provider-login-label")}</h4>
|
||||
|
||||
<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountListGridClass!}</#if>">
|
||||
<#list social.providers as p>
|
||||
<a id="social-${p.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
|
||||
type="button" href="${p.loginUrl}">
|
||||
<#if p.iconClasses?has_content>
|
||||
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
|
||||
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
|
||||
<#else>
|
||||
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
|
||||
</#if>
|
||||
</a>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,38 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("logoutConfirmTitle")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-logout-confirm" class="content-area">
|
||||
<p class="instruction">${msg("logoutConfirmHeader")}</p>
|
||||
|
||||
<form class="form-actions" action="${url.logoutConfirmAction}" method="POST">
|
||||
<input type="hidden" name="session_code" value="${logoutConfirm.code}">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
|
||||
<input tabindex="4"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="confirmLogout" id="kc-logout" type="submit" value="${msg("doLogout")}"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="kc-info-message">
|
||||
<#if logoutConfirm.skipLink>
|
||||
<#else>
|
||||
<#if (client.baseUrl)?has_content>
|
||||
<p><a href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
|
||||
</#if>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,511 @@
|
|||
doLogIn=Sign In
|
||||
doRegister=Register
|
||||
doCancel=Cancel
|
||||
doSubmit=Submit
|
||||
doBack=Back
|
||||
doYes=Yes
|
||||
doNo=No
|
||||
doContinue=Continue
|
||||
doIgnore=Ignore
|
||||
doAccept=Accept
|
||||
doDecline=Decline
|
||||
doForgotPassword=Forgot Password?
|
||||
doClickHere=Click here
|
||||
doImpersonate=Impersonate
|
||||
doTryAgain=Try again
|
||||
doTryAnotherWay=Try Another Way
|
||||
doConfirmDelete=Confirm deletion
|
||||
errorDeletingAccount=Error happened while deleting account
|
||||
deletingAccountForbidden=You do not have enough permissions to delete your own account, contact admin.
|
||||
kerberosNotConfigured=Kerberos Not Configured
|
||||
kerberosNotConfiguredTitle=Kerberos Not Configured
|
||||
bypassKerberosDetail=Either you are not logged in by Kerberos or your browser is not set up for Kerberos login. Please click continue to login in through other means
|
||||
kerberosNotSetUp=Kerberos is not set up. You cannot login.
|
||||
registerTitle=Register
|
||||
loginAccountTitle=Sign in to your account
|
||||
loginTitle=Sign in to {0}
|
||||
loginTitleHtml={0}
|
||||
impersonateTitle={0} Impersonate User
|
||||
impersonateTitleHtml=<strong>{0}</strong> Impersonate User
|
||||
realmChoice=Realm
|
||||
unknownUser=Unknown user
|
||||
loginTotpTitle=Mobile Authenticator Setup
|
||||
loginProfileTitle=Update Account Information
|
||||
loginIdpReviewProfileTitle=Update Account Information
|
||||
loginTimeout=Your login attempt timed out. Login will start from the beginning.
|
||||
reauthenticate=Please re-authenticate to continue
|
||||
oauthGrantTitle=Grant Access to {0}
|
||||
oauthGrantTitleHtml={0}
|
||||
oauthGrantInformation=Make sure you trust {0} by learning how {0} will handle your data.
|
||||
oauthGrantReview=You could review the
|
||||
oauthGrantTos=terms of service.
|
||||
oauthGrantPolicy=privacy policy.
|
||||
errorTitle=We are sorry...
|
||||
errorTitleHtml=We are <strong>sorry</strong> ...
|
||||
emailVerifyTitle=Email verification
|
||||
emailForgotTitle=Forgot Your Password?
|
||||
updateEmailTitle=Update email
|
||||
emailUpdateConfirmationSentTitle=Confirmation email sent
|
||||
emailUpdateConfirmationSent=A confirmation email has been sent to {0}. You must follow the instructions of the former to complete the email update.
|
||||
emailUpdatedTitle=Email updated
|
||||
emailUpdated=The account email has been successfully updated to {0}.
|
||||
updatePasswordTitle=Update password
|
||||
codeSuccessTitle=Success code
|
||||
codeErrorTitle=Error code\: {0}
|
||||
displayUnsupported=Requested display type unsupported
|
||||
browserRequired=Browser required to login
|
||||
browserContinue=Browser required to complete login
|
||||
browserContinuePrompt=Open browser and continue login? [y/n]:
|
||||
browserContinueAnswer=y
|
||||
|
||||
# Transports
|
||||
usb=USB
|
||||
nfc=NFC
|
||||
bluetooth=Bluetooth
|
||||
internal=Internal
|
||||
unknown=Unknown
|
||||
|
||||
termsTitle=Terms and Conditions
|
||||
termsText=<p>Terms and conditions to be defined</p>
|
||||
termsPlainText=Terms and conditions to be defined.
|
||||
|
||||
recaptchaFailed=Invalid Recaptcha
|
||||
recaptchaNotConfigured=Recaptcha is required, but not configured
|
||||
consentDenied=Consent denied.
|
||||
|
||||
noAccount=New user?
|
||||
username=Username
|
||||
usernameOrEmail=Username or email
|
||||
firstName=First name
|
||||
givenName=Given name
|
||||
fullName=Full name
|
||||
lastName=Last name
|
||||
familyName=Family name
|
||||
email=Email
|
||||
password=Password
|
||||
passwordConfirm=Confirm password
|
||||
passwordNew=New Password
|
||||
passwordNewConfirm=New Password confirmation
|
||||
rememberMe=Remember me
|
||||
authenticatorCode=One-time code
|
||||
address=Address
|
||||
street=Street
|
||||
locality=City or Locality
|
||||
region=State, Province, or Region
|
||||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
emailVerified=Email verified
|
||||
website=Web page
|
||||
phoneNumber=Phone number
|
||||
phoneNumberVerified=Phone number verified
|
||||
gender=Gender
|
||||
birthday=Birthdate
|
||||
zoneinfo=Time zone
|
||||
gssDelegationCredential=GSS Delegation Credential
|
||||
logoutOtherSessions=Sign out from other devices
|
||||
|
||||
profileScopeConsentText=User profile
|
||||
emailScopeConsentText=Email address
|
||||
addressScopeConsentText=Address
|
||||
phoneScopeConsentText=Phone number
|
||||
offlineAccessScopeConsentText=Offline Access
|
||||
samlRoleListScopeConsentText=My Roles
|
||||
rolesScopeConsentText=User roles
|
||||
|
||||
restartLoginTooltip=Restart login
|
||||
|
||||
loginTotpIntro=You need to set up a One Time Password generator to access this account
|
||||
loginTotpStep1=Install one of the following applications on your mobile:
|
||||
loginTotpStep2=Open the application and scan the barcode:
|
||||
loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup.
|
||||
loginTotpStep3DeviceName=Provide a Device Name to help you manage your OTP devices.
|
||||
loginTotpManualStep2=Open the application and enter the key:
|
||||
loginTotpManualStep3=Use the following configuration values if the application allows setting them:
|
||||
loginTotpUnableToScan=Unable to scan?
|
||||
loginTotpScanBarcode=Scan barcode?
|
||||
loginCredential=Credential
|
||||
loginOtpOneTime=One-time code
|
||||
loginTotpType=Type
|
||||
loginTotpAlgorithm=Algorithm
|
||||
loginTotpDigits=Digits
|
||||
loginTotpInterval=Interval
|
||||
loginTotpCounter=Counter
|
||||
loginTotpDeviceName=Device Name
|
||||
|
||||
loginTotp.totp=Time-based
|
||||
loginTotp.hotp=Counter-based
|
||||
|
||||
totpAppFreeOTPName=FreeOTP
|
||||
totpAppGoogleName=Google Authenticator
|
||||
totpAppMicrosoftAuthenticatorName=Microsoft Authenticator
|
||||
|
||||
loginChooseAuthenticator=Select login method
|
||||
|
||||
oauthGrantRequest=Do you grant these access privileges?
|
||||
inResource=in
|
||||
|
||||
oauth2DeviceVerificationTitle=Device Login
|
||||
verifyOAuth2DeviceUserCode=Enter the code provided by your device and click Submit
|
||||
oauth2DeviceInvalidUserCodeMessage=Invalid code, please try again.
|
||||
oauth2DeviceExpiredUserCodeMessage=The code has expired. Please go back to your device and try connecting again.
|
||||
oauth2DeviceVerificationCompleteHeader=Device Login Successful
|
||||
oauth2DeviceVerificationCompleteMessage=You may close this browser window and go back to your device.
|
||||
oauth2DeviceVerificationFailedHeader=Device Login Failed
|
||||
oauth2DeviceVerificationFailedMessage=You may close this browser window and go back to your device and try connecting again.
|
||||
oauth2DeviceConsentDeniedMessage=Consent denied for connecting the device.
|
||||
oauth2DeviceAuthorizationGrantDisabledMessage=Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.
|
||||
|
||||
emailVerifyInstruction1=An email with instructions to verify your email address has been sent to your address {0}.
|
||||
emailVerifyInstruction2=Haven''t received a verification code in your email?
|
||||
emailVerifyInstruction3=to re-send the email.
|
||||
|
||||
emailLinkIdpTitle=Link {0}
|
||||
emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
|
||||
emailLinkIdp2=Haven''t received a verification code in your email?
|
||||
emailLinkIdp3=to re-send the email.
|
||||
emailLinkIdp4=If you already verified the email in different browser
|
||||
emailLinkIdp5=to continue.
|
||||
|
||||
backToLogin=« Back to Login
|
||||
|
||||
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
|
||||
emailInstructionUsername=Enter your username and we will send you instructions on how to create a new password.
|
||||
|
||||
copyCodeInstruction=Please copy this code and paste it into your application:
|
||||
|
||||
pageExpiredTitle=Page has expired
|
||||
pageExpiredMsg1=To restart the login process
|
||||
pageExpiredMsg2=To continue the login process
|
||||
|
||||
personalInfo=Personal Info:
|
||||
role_admin=Admin
|
||||
role_realm-admin=Realm Admin
|
||||
role_create-realm=Create realm
|
||||
role_create-client=Create client
|
||||
role_view-realm=View realm
|
||||
role_view-users=View users
|
||||
role_view-applications=View applications
|
||||
role_view-clients=View clients
|
||||
role_view-events=View events
|
||||
role_view-identity-providers=View identity providers
|
||||
role_manage-realm=Manage realm
|
||||
role_manage-users=Manage users
|
||||
role_manage-applications=Manage applications
|
||||
role_manage-identity-providers=Manage identity providers
|
||||
role_manage-clients=Manage clients
|
||||
role_manage-events=Manage events
|
||||
role_view-profile=View profile
|
||||
role_manage-account=Manage account
|
||||
role_manage-account-links=Manage account links
|
||||
role_read-token=Read token
|
||||
role_offline-access=Offline access
|
||||
client_account=Account
|
||||
client_account-console=Account Console
|
||||
client_security-admin-console=Security Admin Console
|
||||
client_admin-cli=Admin CLI
|
||||
client_realm-management=Realm Management
|
||||
client_broker=Broker
|
||||
|
||||
requiredFields=Required fields
|
||||
|
||||
invalidUserMessage=Invalid username or password.
|
||||
invalidUsernameMessage=Invalid username.
|
||||
invalidUsernameOrEmailMessage=Invalid username or email.
|
||||
invalidPasswordMessage=Invalid password.
|
||||
invalidEmailMessage=Invalid email address.
|
||||
accountDisabledMessage=Account is disabled, contact your administrator.
|
||||
accountTemporarilyDisabledMessage=Account is temporarily disabled; contact your administrator or retry later.
|
||||
expiredCodeMessage=Login timeout. Please sign in again.
|
||||
expiredActionMessage=Action expired. Please continue with login now.
|
||||
expiredActionTokenNoSessionMessage=Action expired.
|
||||
expiredActionTokenSessionExistsMessage=Action expired. Please start again.
|
||||
sessionLimitExceeded=There are too many sessions
|
||||
|
||||
missingFirstNameMessage=Please specify first name.
|
||||
missingLastNameMessage=Please specify last name.
|
||||
missingEmailMessage=Please specify email.
|
||||
missingUsernameMessage=Please specify username.
|
||||
missingPasswordMessage=Please specify password.
|
||||
missingTotpMessage=Please specify authenticator code.
|
||||
missingTotpDeviceNameMessage=Please specify device name.
|
||||
notMatchPasswordMessage=Passwords don''t match.
|
||||
|
||||
error-invalid-value=Invalid value.
|
||||
error-invalid-blank=Please specify value.
|
||||
error-empty=Please specify value.
|
||||
error-invalid-length=Length must be between {1} and {2}.
|
||||
error-invalid-length-too-short=Minimal length is {1}.
|
||||
error-invalid-length-too-long=Maximal length is {2}.
|
||||
error-invalid-email=Invalid email address.
|
||||
error-invalid-number=Invalid number.
|
||||
error-number-out-of-range=Number must be between {1} and {2}.
|
||||
error-number-out-of-range-too-small=Number must have minimal value of {1}.
|
||||
error-number-out-of-range-too-big=Number must have maximal value of {2}.
|
||||
error-pattern-no-match=Invalid value.
|
||||
error-invalid-uri=Invalid URL.
|
||||
error-invalid-uri-scheme=Invalid URL scheme.
|
||||
error-invalid-uri-fragment=Invalid URL fragment.
|
||||
error-user-attribute-required=Please specify this field.
|
||||
error-invalid-date=Invalid date.
|
||||
error-user-attribute-read-only=This field is read only.
|
||||
error-username-invalid-character=Value contains invalid character.
|
||||
error-person-name-invalid-character=Value contains invalid character.
|
||||
|
||||
invalidPasswordExistingMessage=Invalid existing password.
|
||||
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
|
||||
invalidPasswordConfirmMessage=Password confirmation doesn''t match.
|
||||
invalidTotpMessage=Invalid authenticator code.
|
||||
|
||||
usernameExistsMessage=Username already exists.
|
||||
emailExistsMessage=Email already exists.
|
||||
|
||||
federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
|
||||
federatedIdentityUnavailableMessage=User {0} authenticated with identity provider {1} does not exist. Please contact your administrator.
|
||||
|
||||
confirmLinkIdpTitle=Account already exists
|
||||
federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
|
||||
federatedIdentityConfirmReauthenticateMessage=Authenticate to link your account with {0}
|
||||
nestedFirstBrokerFlowMessage=The {0} user {1} is not linked to any known user.
|
||||
confirmLinkIdpReviewProfile=Review profile
|
||||
confirmLinkIdpContinue=Add to existing account
|
||||
|
||||
configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
|
||||
configureBackupCodesMessage=You need to set up Backup Codes to activate your account.
|
||||
updateProfileMessage=You need to update your user profile to activate your account.
|
||||
updatePasswordMessage=You need to change your password to activate your account.
|
||||
updateEmailMessage=You need to update your email address to activate your account.
|
||||
resetPasswordMessage=You need to change your password.
|
||||
verifyEmailMessage=You need to verify your email address to activate your account.
|
||||
linkIdpMessage=You need to verify your email address to link your account with {0}.
|
||||
|
||||
emailSentMessage=You should receive an email shortly with further instructions.
|
||||
emailSendErrorMessage=Failed to send email, please try again later.
|
||||
|
||||
accountUpdatedMessage=Your account has been updated.
|
||||
accountPasswordUpdatedMessage=Your password has been updated.
|
||||
|
||||
delegationCompleteHeader=Login Successful
|
||||
delegationCompleteMessage=You may close this browser window and go back to your console application.
|
||||
delegationFailedHeader=Login Failed
|
||||
delegationFailedMessage=You may close this browser window and go back to your console application and try logging in again.
|
||||
|
||||
noAccessMessage=No access
|
||||
|
||||
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
|
||||
invalidPasswordMaxLengthMessage=Invalid password: maximum length {0}.
|
||||
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
|
||||
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
|
||||
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
|
||||
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
|
||||
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
|
||||
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
|
||||
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
|
||||
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
|
||||
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
|
||||
|
||||
failedToProcessResponseMessage=Failed to process response
|
||||
httpsRequiredMessage=HTTPS required
|
||||
realmNotEnabledMessage=Realm not enabled
|
||||
invalidRequestMessage=Invalid Request
|
||||
successLogout=You are logged out
|
||||
failedLogout=Logout failed
|
||||
unknownLoginRequesterMessage=Unknown login requester
|
||||
loginRequesterNotEnabledMessage=Login requester not enabled
|
||||
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
|
||||
standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
|
||||
implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
|
||||
invalidRedirectUriMessage=Invalid redirect uri
|
||||
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
|
||||
invalidRequesterMessage=Invalid requester
|
||||
registrationNotAllowedMessage=Registration not allowed
|
||||
resetCredentialNotAllowedMessage=Reset Credential not allowed
|
||||
|
||||
permissionNotApprovedMessage=Permission not approved.
|
||||
noRelayStateInResponseMessage=No relay state in response from identity provider.
|
||||
insufficientPermissionMessage=Insufficient permissions to link identities.
|
||||
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
|
||||
couldNotObtainTokenMessage=Could not obtain token from identity provider.
|
||||
unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
|
||||
unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
|
||||
identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
|
||||
couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
|
||||
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
|
||||
invalidAccessCodeMessage=Invalid access code.
|
||||
sessionNotActiveMessage=Session not active.
|
||||
invalidCodeMessage=An error occurred, please login again through your application.
|
||||
cookieNotFoundMessage=Cookie not found. Please make sure cookies are enabled in your browser.
|
||||
insufficientLevelOfAuthentication=The requested level of authentication has not been satisfied.
|
||||
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
|
||||
identityProviderMissingStateMessage=Missing state parameter in response from identity provider.
|
||||
identityProviderInvalidResponseMessage=Invalid response from identity provider.
|
||||
identityProviderInvalidSignatureMessage=Invalid signature in response from identity provider.
|
||||
identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
|
||||
identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login.
|
||||
staleCodeMessage=This page is no longer valid, please go back to your application and sign in again
|
||||
realmSupportsNoCredentialsMessage=Realm does not support any credential type.
|
||||
credentialSetupRequired=Cannot login, credential setup required.
|
||||
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
|
||||
emailVerifiedMessage=Your email address has been verified.
|
||||
staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.
|
||||
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
|
||||
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
|
||||
confirmEmailAddressVerification=Confirm validity of e-mail address {0}.
|
||||
confirmExecutionOfActions=Perform the following action(s)
|
||||
|
||||
locale_ar=\u0639\u0631\u0628\u064A
|
||||
locale_ca=Catal\u00E0
|
||||
locale_cs=\u010Ce\u0161tina
|
||||
locale_da=Dansk
|
||||
locale_de=Deutsch
|
||||
locale_en=English
|
||||
locale_es=Espa\u00F1ol
|
||||
locale_fr=Fran\u00E7ais
|
||||
locale_hu=Magyar
|
||||
locale_it=Italiano
|
||||
locale_ja=\u65E5\u672C\u8A9E
|
||||
locale_lt=Lietuvi\u0173
|
||||
locale_nl=Nederlands
|
||||
locale_no=Norsk
|
||||
locale_pl=Polski
|
||||
locale_pt_BR=Portugu\u00EAs (Brasil)
|
||||
locale_pt-BR=Portugu\u00EAs (Brasil)
|
||||
locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
|
||||
locale_sk=Sloven\u010Dina
|
||||
locale_sv=Svenska
|
||||
locale_tr=T\u00FCrk\u00E7e
|
||||
locale_zh-CN=\u4E2D\u6587\u7B80\u4F53
|
||||
locale_fi=Suomi
|
||||
|
||||
backToApplication=« Back to Application
|
||||
missingParameterMessage=Missing parameters\: {0}
|
||||
clientNotFoundMessage=Client not found.
|
||||
clientDisabledMessage=Client disabled.
|
||||
invalidParameterMessage=Invalid parameter\: {0}
|
||||
alreadyLoggedIn=You are already logged in.
|
||||
differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please sign out first.
|
||||
brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid.
|
||||
proceedWithAction=» Click here to proceed
|
||||
acrNotFulfilled=Authentication requirements not fulfilled
|
||||
|
||||
requiredAction.CONFIGURE_TOTP=Configure OTP
|
||||
requiredAction.TERMS_AND_CONDITIONS=Terms and Conditions
|
||||
requiredAction.UPDATE_PASSWORD=Update Password
|
||||
requiredAction.UPDATE_PROFILE=Update Profile
|
||||
requiredAction.VERIFY_EMAIL=Verify Email
|
||||
requiredAction.CONFIGURE_RECOVERY_AUTHN_CODES=Generate Recovery Codes
|
||||
requiredAction.webauthn-register-passwordless=Webauthn Register Passwordless
|
||||
|
||||
invalidTokenRequiredActions=Required actions included in the link are not valid
|
||||
|
||||
doX509Login=You will be logged in as\:
|
||||
clientCertificate=X509 client certificate\:
|
||||
noCertificate=[No Certificate]
|
||||
|
||||
|
||||
pageNotFound=Page not found
|
||||
internalServerError=An internal server error has occurred
|
||||
|
||||
console-username=Username:
|
||||
console-password=Password:
|
||||
console-otp=One Time Password:
|
||||
console-new-password=New Password:
|
||||
console-confirm-password=Confirm Password:
|
||||
console-update-password=Update of your password is required.
|
||||
console-verify-email=You need to verify your email address. We sent an email to {0} that contains a verification code. Please enter this code into the input below.
|
||||
console-email-code=Email Code:
|
||||
console-accept-terms=Accept Terms? [y/n]:
|
||||
console-accept=y
|
||||
|
||||
# Openshift messages
|
||||
openshift.scope.user_info=User information
|
||||
openshift.scope.user_check-access=User access information
|
||||
openshift.scope.user_full=Full Access
|
||||
openshift.scope.list-projects=List projects
|
||||
|
||||
# SAML authentication
|
||||
saml.post-form.title=Authentication Redirect
|
||||
saml.post-form.message=Redirecting, please wait.
|
||||
saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.
|
||||
saml.artifactResolutionServiceInvalidResponse=Unable to resolve artifact.
|
||||
|
||||
#authenticators
|
||||
otp-display-name=Authenticator Application
|
||||
otp-help-text=Enter a verification code from authenticator application.
|
||||
password-display-name=Password
|
||||
password-help-text=Sign in by entering your password.
|
||||
auth-username-form-display-name=Username
|
||||
auth-username-form-help-text=Start sign in by entering your username
|
||||
auth-username-password-form-display-name=Username and password
|
||||
auth-username-password-form-help-text=Sign in by entering your username and password.
|
||||
|
||||
# Recovery Codes
|
||||
auth-recovery-authn-code-form-display-name=Recovery Authentication Code
|
||||
auth-recovery-authn-code-form-help-text=Enter a recovery authentication code from a previously generated list.
|
||||
auth-recovery-code-info-message=Enter the specified recovery code.
|
||||
auth-recovery-code-prompt=Recovery code #{0}
|
||||
auth-recovery-code-header=Login with a recovery authentication code
|
||||
recovery-codes-error-invalid=Invalid recovery authentication code
|
||||
recovery-code-config-header=Recovery Authentication Codes
|
||||
recovery-code-config-warning-title=These recovery codes won't appear again after leaving this page
|
||||
recovery-code-config-warning-message=Make sure to print, download, or copy them to a password manager and keep them save. Canceling this setup will remove these recovery codes from your account.
|
||||
recovery-codes-print=Print
|
||||
recovery-codes-download=Download
|
||||
recovery-codes-copy=Copy
|
||||
recovery-codes-copied=Copied
|
||||
recovery-codes-confirmation-message=I have saved these codes somewhere safe
|
||||
recovery-codes-action-complete=Complete setup
|
||||
recovery-codes-action-cancel=Cancel setup
|
||||
recovery-codes-download-file-header=Keep these recovery codes somewhere safe.
|
||||
recovery-codes-download-file-description=Recovery codes are single-use passcodes that allow you to sign in to your account if you do not have access to your authenticator.
|
||||
recovery-codes-download-file-date= These codes were generated on
|
||||
recovery-codes-label-default=Recovery codes
|
||||
|
||||
# WebAuthn
|
||||
webauthn-display-name=Security Key
|
||||
webauthn-help-text=Use your security key to sign in.
|
||||
webauthn-passwordless-display-name=Security Key
|
||||
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
|
||||
webauthn-login-title=Security Key login
|
||||
webauthn-registration-title=Security Key Registration
|
||||
webauthn-available-authenticators=Available Security Keys
|
||||
webauthn-unsupported-browser-text=WebAuthn is not supported by this browser. Try another one or contact your administrator.
|
||||
webauthn-doAuthenticate=Sign in with Security Key
|
||||
webauthn-createdAt-label=Created
|
||||
|
||||
# WebAuthn Error
|
||||
webauthn-error-title=Security Key Error
|
||||
webauthn-error-registration=Failed to register your Security key.<br/> {0}
|
||||
webauthn-error-api-get=Failed to authenticate by the Security key.<br/> {0}
|
||||
webauthn-error-different-user=First authenticated user is not the one authenticated by the Security key.
|
||||
webauthn-error-auth-verification=Security key authentication result is invalid.<br/> {0}
|
||||
webauthn-error-register-verification=Security key registration result is invalid.<br/> {0}
|
||||
webauthn-error-user-not-found=Unknown user authenticated by the Security key.
|
||||
|
||||
# Identity provider
|
||||
identity-provider-redirector=Connect with another Identity Provider
|
||||
identity-provider-login-label=Or sign in with
|
||||
idp-email-verification-display-name=Email Verification
|
||||
idp-email-verification-help-text=Link your account by validating your email.
|
||||
idp-username-password-form-display-name=Username and password
|
||||
idp-username-password-form-help-text=Link your account by logging in.
|
||||
|
||||
finalDeletionConfirmation=If you delete your account, it cannot be restored. To keep your account, click Cancel.
|
||||
irreversibleAction=This action is irreversible
|
||||
deleteAccountConfirm=Delete account confirmation
|
||||
|
||||
deletingImplies=Deleting your account implies:
|
||||
errasingData=Erasing all your data
|
||||
loggingOutImmediately=Logging you out immediately
|
||||
accountUnusable=Any subsequent use of the application will not be possible with this account
|
||||
userDeletedSuccessfully=User deleted successfully
|
||||
|
||||
access-denied=Access denied
|
||||
access-denied-when-idp-auth=Access denied when authenticating with {0}
|
||||
|
||||
frontchannel-logout.title=Logging out
|
||||
frontchannel-logout.message=You are logging out from following apps
|
||||
logoutConfirmTitle=Logging out
|
||||
logoutConfirmHeader=Do you want to logout?
|
||||
doLogout=Logout
|
||||
|
||||
readOnlyUsernameMessage=You can''t update your username as it is read-only.
|
|
@ -0,0 +1,74 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("registerTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields; callback, attribute>
|
||||
<#if callback = "afterField">
|
||||
<#-- render password fields just under the username or email (if used as username) -->
|
||||
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm"
|
||||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
|
||||
name="password-confirm"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password-confirm')>
|
||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
</@userProfileCommons.userProfileFormFields>
|
||||
|
||||
<#if recaptchaRequired??>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,141 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section>
|
||||
<#if section = "header">
|
||||
${msg("registerTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName"
|
||||
value="${(register.formData.firstName!'')}"
|
||||
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('firstName')>
|
||||
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
|
||||
value="${(register.formData.lastName!'')}"
|
||||
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('lastName')>
|
||||
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" class="${properties.kcInputClass!}" name="email"
|
||||
value="${(register.formData.email!'')}" autocomplete="email"
|
||||
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('email')>
|
||||
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('email'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<#if !realm.registrationEmailAsUsername>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" class="${properties.kcInputClass!}" name="username"
|
||||
value="${(register.formData.username!'')}" autocomplete="username"
|
||||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if passwordRequired??>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="password-confirm"
|
||||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
|
||||
name="password-confirm"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('password-confirm')>
|
||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#if recaptchaRequired??>
|
||||
<div class="form-group">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,209 @@
|
|||
body {
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
background: #080e08;
|
||||
color: #f6f6f6;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #4af626;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 10px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
background: #3a7920;
|
||||
color: #f6f6f6;
|
||||
|
||||
margin: auto auto 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #2cacb0;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #b4778f;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background: #fcfffc;
|
||||
color: #211c1b;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #4c982a;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #1f7b7e;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #7c5263;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: #ffffff;
|
||||
color: #211c1b;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 154, 0, 1) 10%,
|
||||
rgba(208, 222, 33, 1) 20%,
|
||||
rgba(79, 220, 74, 1) 30%,
|
||||
rgba(63, 218, 216, 1) 40%,
|
||||
rgba(47, 201, 226, 1) 50%,
|
||||
rgba(28, 127, 238, 1) 60%,
|
||||
rgba(95, 21, 242, 1) 70%,
|
||||
rgba(186, 12, 248, 1) 80%,
|
||||
rgba(251, 7, 217, 1) 90%,
|
||||
rgba(255, 0, 0, 1) 100%
|
||||
);
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#header-content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
#header-content h1 {
|
||||
color: black;
|
||||
margin: auto auto auto 1rem;
|
||||
}
|
||||
|
||||
#header-content img {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
span.copyleft {
|
||||
display: inline-block;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
footer a:link, footer a:visited {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
background: #332c29;
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: #332c29;
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
textarea.big {
|
||||
width: 100%;
|
||||
height: 5em;
|
||||
}
|
||||
|
||||
.error, .required {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
#nav {
|
||||
margin: auto 0 auto auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#nav a {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#header-content a:link {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
#header-content a:visited {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#header-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
label.field-with-error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
img.stats {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
padding: 5px;
|
||||
}
|
||||
th {
|
||||
border-bottom: solid 2px currentColor;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
border-right: solid 2px currentColor;
|
||||
border-left: none;
|
||||
}
|
||||
td {
|
||||
border-right: solid 2px currentColor;
|
||||
border-left: solid 2px currentColor;
|
||||
}
|
||||
td:last-child {
|
||||
border-right: none;
|
||||
border-left: solid 2px currentColor;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
.responsive-wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
|
@ -0,0 +1,114 @@
|
|||
// for embedded scripts, quoted and modified from https://github.com/swansontec/rfc4648.js by William Swanson
|
||||
'use strict';
|
||||
var base64url = base64url || {};
|
||||
(function(base64url) {
|
||||
|
||||
function parse (string, encoding, opts = {}) {
|
||||
// Build the character lookup table:
|
||||
if (!encoding.codes) {
|
||||
encoding.codes = {};
|
||||
for (let i = 0; i < encoding.chars.length; ++i) {
|
||||
encoding.codes[encoding.chars[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// The string must have a whole number of bytes:
|
||||
if (!opts.loose && (string.length * encoding.bits) & 7) {
|
||||
throw new SyntaxError('Invalid padding');
|
||||
}
|
||||
|
||||
// Count the padding bytes:
|
||||
let end = string.length;
|
||||
while (string[end - 1] === '=') {
|
||||
--end;
|
||||
|
||||
// If we get a whole number of bytes, there is too much padding:
|
||||
if (!opts.loose && !(((string.length - end) * encoding.bits) & 7)) {
|
||||
throw new SyntaxError('Invalid padding');
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the output:
|
||||
const out = new (opts.out || Uint8Array)(((end * encoding.bits) / 8) | 0);
|
||||
|
||||
// Parse the data:
|
||||
let bits = 0; // Number of bits currently in the buffer
|
||||
let buffer = 0; // Bits waiting to be written out, MSB first
|
||||
let written = 0; // Next byte to write
|
||||
for (let i = 0; i < end; ++i) {
|
||||
// Read one character from the string:
|
||||
const value = encoding.codes[string[i]];
|
||||
if (value === void 0) {
|
||||
throw new SyntaxError('Invalid character ' + string[i]);
|
||||
}
|
||||
|
||||
// Append the bits to the buffer:
|
||||
buffer = (buffer << encoding.bits) | value;
|
||||
bits += encoding.bits;
|
||||
|
||||
// Write out some bits if the buffer has a byte's worth:
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out[written++] = 0xff & (buffer >> bits);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we have received just enough bits:
|
||||
if (bits >= encoding.bits || 0xff & (buffer << (8 - bits))) {
|
||||
throw new SyntaxError('Unexpected end of data');
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
function stringify (data, encoding, opts = {}) {
|
||||
const { pad = true } = opts;
|
||||
const mask = (1 << encoding.bits) - 1;
|
||||
let out = '';
|
||||
|
||||
let bits = 0; // Number of bits currently in the buffer
|
||||
let buffer = 0; // Bits waiting to be written out, MSB first
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
// Slurp data into the buffer:
|
||||
buffer = (buffer << 8) | (0xff & data[i]);
|
||||
bits += 8;
|
||||
|
||||
// Write out as much as we can:
|
||||
while (bits > encoding.bits) {
|
||||
bits -= encoding.bits;
|
||||
out += encoding.chars[mask & (buffer >> bits)];
|
||||
}
|
||||
}
|
||||
|
||||
// Partial character:
|
||||
if (bits) {
|
||||
out += encoding.chars[mask & (buffer << (encoding.bits - bits))];
|
||||
}
|
||||
|
||||
// Add padding characters until we hit a byte boundary:
|
||||
if (pad) {
|
||||
while ((out.length * encoding.bits) & 7) {
|
||||
out += '=';
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
const encoding = {
|
||||
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
|
||||
bits: 6
|
||||
}
|
||||
|
||||
base64url.decode = function (string, opts) {
|
||||
return parse(string, encoding, opts);
|
||||
}
|
||||
|
||||
base64url.encode = function (data, opts) {
|
||||
return stringify(data, encoding, opts)
|
||||
}
|
||||
|
||||
return base64url;
|
||||
}(base64url));
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
${msg("saml.post-form.title")}
|
||||
<#elseif section = "form">
|
||||
<script>window.onload = function() {document.forms[0].submit()};</script>
|
||||
<p>${msg("saml.post-form.message")}</p>
|
||||
<form name="saml-post-binding" method="post" action="${samlPost.url}">
|
||||
<#if samlPost.SAMLRequest??>
|
||||
<input type="hidden" name="SAMLRequest" value="${samlPost.SAMLRequest}"/>
|
||||
</#if>
|
||||
<#if samlPost.SAMLResponse??>
|
||||
<input type="hidden" name="SAMLResponse" value="${samlPost.SAMLResponse}"/>
|
||||
</#if>
|
||||
<#if samlPost.relayState??>
|
||||
<input type="hidden" name="RelayState" value="${samlPost.relayState}"/>
|
||||
</#if>
|
||||
|
||||
<noscript>
|
||||
<p>${msg("saml.post-form.js-disabled")}</p>
|
||||
<input type="submit" value="${msg("doContinue")}"/>
|
||||
</noscript>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,39 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=false; section>
|
||||
<#if section = "header" || section = "show-username">
|
||||
<script type="text/javascript">
|
||||
function fillAndSubmit(authExecId) {
|
||||
document.getElementById('authexec-hidden-input').value = authExecId;
|
||||
document.getElementById('kc-select-credential-form').submit();
|
||||
}
|
||||
</script>
|
||||
<#if section = "header">
|
||||
${msg("loginChooseAuthenticator")}
|
||||
</#if>
|
||||
<#elseif section = "form">
|
||||
<div class="responsive-wrapper">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<#list auth.authenticationSelections as authenticationSelection>
|
||||
<tr>
|
||||
<td>${msg('${authenticationSelection.displayName}')}</td>
|
||||
<td>${msg('${authenticationSelection.helpText}')}</td>
|
||||
<td>
|
||||
<form action="${url.loginAction}" method="post">
|
||||
<input type="hidden" name="authenticationExecution"
|
||||
value="${authenticationSelection.authExecId}"/>
|
||||
<input type="submit" value="Use This Method"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
|
||||
<!DOCTYPE html>
|
||||
<html class="${properties.kcHtmlClass!}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<#if properties.meta?has_content>
|
||||
<#list properties.meta?split(' ') as meta>
|
||||
<meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
|
||||
</#list>
|
||||
</#if>
|
||||
<title>${msg("loginTitle",(realm.displayName!''))}</title>
|
||||
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
|
||||
<#if properties.stylesCommon?has_content>
|
||||
<#list properties.stylesCommon?split(' ') as style>
|
||||
<link href="${url.resourcesCommonPath}/${style}" rel="stylesheet" />
|
||||
</#list>
|
||||
</#if>
|
||||
<#if properties.styles?has_content>
|
||||
<#list properties.styles?split(' ') as style>
|
||||
<link href="${url.resourcesPath}/${style}" rel="stylesheet" />
|
||||
</#list>
|
||||
</#if>
|
||||
<#if properties.scripts?has_content>
|
||||
<#list properties.scripts?split(' ') as script>
|
||||
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if scripts??>
|
||||
<#list scripts as script>
|
||||
<script src="${script}" type="text/javascript"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
</head>
|
||||
|
||||
<body class="${properties.kcBodyClass!}">
|
||||
<header>
|
||||
<div id="header-content">
|
||||
<img src="${url.resourcesPath}/img/favicon.ico" width="32" height="32" alt="UNIX.dog"/>
|
||||
<h1>${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</h1>
|
||||
<div id="nav">
|
||||
<!-- TODO: nav -->
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="${properties.kcLoginClass!}">
|
||||
<div class="${properties.kcFormCardClass!}">
|
||||
<!--
|
||||
<#if realm.internationalizationEnabled && locale.supported?size gt 1>
|
||||
<div class="${properties.kcLocaleMainClass!}" id="kc-locale">
|
||||
<div id="kc-locale-wrapper" class="${properties.kcLocaleWrapperClass!}">
|
||||
<div id="kc-locale-dropdown" class="${properties.kcLocaleDropDownClass!}">
|
||||
<a href="#" id="kc-current-locale-link">${locale.current}</a>
|
||||
<ul class="${properties.kcLocaleListClass!}">
|
||||
<#list locale.supported as l>
|
||||
<li class="${properties.kcLocaleListItemClass!}">
|
||||
<a class="${properties.kcLocaleItemClass!}" href="${l.url}">${l.label}</a>
|
||||
</li>
|
||||
</#list>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
-->
|
||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||
<#if displayRequiredFields>
|
||||
<div class="${properties.kcContentWrapperClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!} subtitle">
|
||||
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<h1 id="kc-page-title"><#nested "header"></h1>
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
<h1 id="kc-page-title"><#nested "header"></h1>
|
||||
</#if>
|
||||
<#else>
|
||||
<#if displayRequiredFields>
|
||||
<div class="${properties.kcContentWrapperClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!} subtitle">
|
||||
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<#nested "show-username">
|
||||
<div id="kc-username" class="${properties.kcFormGroupClass!}">
|
||||
<p>Logging in as:
|
||||
<b><label
|
||||
id="kc-attempted-username">${auth.attemptedUsername}</label></b>.
|
||||
<a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
|
||||
<span
|
||||
class="kc-tooltip-text">${msg("restartLoginTooltip")}.</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
<#nested "show-username">
|
||||
<div id="kc-username" class="${properties.kcFormGroupClass!}">
|
||||
<p>Logging in as:
|
||||
<b><label
|
||||
id="kc-attempted-username">${auth.attemptedUsername}</label></b>.
|
||||
<a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
|
||||
<span
|
||||
class="kc-tooltip-text">${msg("restartLoginTooltip")}.</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
<div id="kc-content">
|
||||
<div id="kc-content-wrapper">
|
||||
|
||||
<#-- App-initiated actions should not see warning messages about the need to complete the action -->
|
||||
<#-- during login. -->
|
||||
<#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
|
||||
<div class="alert-${message.type} ${properties.kcAlertClass!} pf-m-<#if message.type = 'error'>danger<#else>${message.type}</#if>">
|
||||
<span class="${properties.kcAlertTitleClass!}">${kcSanitize(message.summary)?no_esc}</span>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<#nested "form">
|
||||
|
||||
<#if auth?has_content && auth.showTryAnotherWayLink()>
|
||||
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" name="tryAnotherWay" value="on"/>
|
||||
<input type="submit"
|
||||
value="${msg("doTryAnotherWay")}"/>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
<#if displayInfo>
|
||||
<div id="kc-info" class="${properties.kcSignUpClass!}">
|
||||
<div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
|
||||
<#nested "info">
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
🄯 <a
|
||||
href="https://git.unix.dog/UNIX.dog/website">Copyleft</a> UNIX.dog,
|
||||
2023. All pages licensed under CC BY-NC-SA 4.0.
|
||||
<br>
|
||||
Please follow <a href="/rules">all rules</a> while using these services.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</#macro>
|
|
@ -0,0 +1,15 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<#if section = "header">
|
||||
${msg("termsTitle")}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-terms-text">
|
||||
${kcSanitize(msg("termsText"))?no_esc}
|
||||
</div>
|
||||
<form class="form-actions" action="${url.loginAction}" method="POST">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="accept" id="kc-accept" type="submit" value="${msg("doAccept")}"/>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-decline" type="submit" value="${msg("doDecline")}"/>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
styles=css/main.css
|
||||
|
||||
kcInputErrorMessageClass=error
|
|
@ -0,0 +1,42 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('email'); section>
|
||||
<#if section = "header">
|
||||
${msg("updateEmailTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-email-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="email" name="email" value="${(email.value!'')}"
|
||||
class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('email')>
|
||||
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('email'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,28 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||
<#if section = "header">
|
||||
${msg("loginProfileTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
|
||||
<@userProfileCommons.userProfileFormFields/>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" formnovalidate/>${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,187 @@
|
|||
<#macro userProfileFormFields>
|
||||
<#assign currentGroup="">
|
||||
|
||||
<#list profile.attributes as attribute>
|
||||
|
||||
<#assign groupName = attribute.group!"">
|
||||
<#if groupName != currentGroup>
|
||||
<#assign currentGroup=groupName>
|
||||
<#if currentGroup != "" >
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
|
||||
<#assign groupDisplayHeader=attribute.groupDisplayHeader!"">
|
||||
<#if groupDisplayHeader != "">
|
||||
<#assign groupHeaderText=advancedMsg(attribute.groupDisplayHeader)!groupName>
|
||||
<#else>
|
||||
<#assign groupHeaderText=groupName>
|
||||
</#if>
|
||||
<div class="${properties.kcContentWrapperClass!}">
|
||||
<label id="header-${groupName}" class="${kcFormGroupHeader!}">${groupHeaderText}</label>
|
||||
</div>
|
||||
|
||||
<#assign groupDisplayDescription=attribute.groupDisplayDescription!"">
|
||||
<#if groupDisplayDescription != "">
|
||||
<#assign groupDescriptionText=advancedMsg(attribute.groupDisplayDescription)!"">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label id="description-${groupName}" class="${properties.kcLabelClass!}">${groupDescriptionText}</label>
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
<#nested "beforeField" attribute>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="${attribute.name}" class="${properties.kcLabelClass!}">${advancedMsg(attribute.displayName!'')}</label>
|
||||
<#if attribute.required>*</#if>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<#if attribute.annotations.inputHelperTextBefore??>
|
||||
<div class="${properties.kcInputHelperTextBeforeClass!}" id="form-help-text-before-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextBefore))?no_esc}</div>
|
||||
</#if>
|
||||
<@inputFieldByType attribute=attribute/>
|
||||
<#if messagesPerField.existsError('${attribute.name}')>
|
||||
<span id="input-error-${attribute.name}" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('${attribute.name}'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
<#if attribute.annotations.inputHelperTextAfter??>
|
||||
<div class="${properties.kcInputHelperTextAfterClass!}" id="form-help-text-after-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextAfter))?no_esc}</div>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<#nested "afterField" attribute>
|
||||
</#list>
|
||||
</#macro>
|
||||
|
||||
<#macro inputFieldByType attribute>
|
||||
<#switch attribute.annotations.inputType!''>
|
||||
<#case 'textarea'>
|
||||
<@textareaTag attribute=attribute/>
|
||||
<#break>
|
||||
<#case 'select'>
|
||||
<#case 'multiselect'>
|
||||
<@selectTag attribute=attribute/>
|
||||
<#break>
|
||||
<#case 'select-radiobuttons'>
|
||||
<#case 'multiselect-checkboxes'>
|
||||
<@inputTagSelects attribute=attribute/>
|
||||
<#break>
|
||||
<#default>
|
||||
<@inputTag attribute=attribute/>
|
||||
</#switch>
|
||||
</#macro>
|
||||
|
||||
<#macro inputTag attribute>
|
||||
<input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(attribute.value!'')}" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if>
|
||||
<#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}"</#if>
|
||||
<#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if>
|
||||
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
|
||||
<#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}"</#if>
|
||||
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
|
||||
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if>
|
||||
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||
/>
|
||||
</#macro>
|
||||
|
||||
<#macro inputTagType attribute>
|
||||
<#compress>
|
||||
<#if attribute.annotations.inputType??>
|
||||
<#if attribute.annotations.inputType?starts_with("html5-")>
|
||||
${attribute.annotations.inputType[6..]}
|
||||
<#else>
|
||||
${attribute.annotations.inputType}
|
||||
</#if>
|
||||
<#else>
|
||||
text
|
||||
</#if>
|
||||
</#compress>
|
||||
</#macro>
|
||||
|
||||
<#macro textareaTag attribute>
|
||||
<textarea id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.annotations.inputTypeCols??>cols="${attribute.annotations.inputTypeCols}"</#if>
|
||||
<#if attribute.annotations.inputTypeRows??>rows="${attribute.annotations.inputTypeRows}"</#if>
|
||||
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
|
||||
>${(attribute.value!'')}</textarea>
|
||||
</#macro>
|
||||
|
||||
<#macro selectTag attribute>
|
||||
<select id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
||||
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||
>
|
||||
<#if attribute.annotations.inputType=='select'>
|
||||
<option value=""></option>
|
||||
</#if>
|
||||
|
||||
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||
<#assign options=attribute.validators.options.options>
|
||||
</#if>
|
||||
|
||||
<#if options??>
|
||||
<#list options as option>
|
||||
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
||||
</#list>
|
||||
</#if>
|
||||
</select>
|
||||
</#macro>
|
||||
|
||||
<#macro inputTagSelects attribute>
|
||||
<#if attribute.annotations.inputType=='select-radiobuttons'>
|
||||
<#assign inputType='radio'>
|
||||
<#assign classDiv=properties.kcInputClassRadio!>
|
||||
<#assign classInput=properties.kcInputClassRadioInput!>
|
||||
<#assign classLabel=properties.kcInputClassRadioLabel!>
|
||||
<#else>
|
||||
<#assign inputType='checkbox'>
|
||||
<#assign classDiv=properties.kcInputClassCheckbox!>
|
||||
<#assign classInput=properties.kcInputClassCheckboxInput!>
|
||||
<#assign classLabel=properties.kcInputClassCheckboxLabel!>
|
||||
</#if>
|
||||
|
||||
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||
<#assign options=attribute.validators.options.options>
|
||||
</#if>
|
||||
|
||||
<#if options??>
|
||||
<#list options as option>
|
||||
<div class="${classDiv}">
|
||||
<input type="${inputType}" id="${attribute.name}-${option}" name="${attribute.name}" value="${option}" class="${classInput}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.values?seq_contains(option)>checked</#if>
|
||||
/>
|
||||
<label for="${attribute.name}-${option}" class="${classLabel}<#if attribute.readOnly> ${properties.kcInputClassRadioCheckboxLabelDisabled!}</#if>"><@selectOptionLabelText attribute=attribute option=option/></label>
|
||||
</div>
|
||||
</#list>
|
||||
</#if>
|
||||
</select>
|
||||
</#macro>
|
||||
|
||||
<#macro selectOptionLabelText attribute option>
|
||||
<#compress>
|
||||
<#if attribute.annotations.inputOptionLabels??>
|
||||
${advancedMsg(attribute.annotations.inputOptionLabels[option]!option)}
|
||||
<#else>
|
||||
<#if attribute.annotations.inputOptionLabelsI18nPrefix??>
|
||||
${msg(attribute.annotations.inputOptionLabelsI18nPrefix + '.' + option)}
|
||||
<#else>
|
||||
${option}
|
||||
</#if>
|
||||
</#if>
|
||||
</#compress>
|
||||
</#macro>
|
|
@ -0,0 +1,168 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
title
|
||||
<#elseif section = "header">
|
||||
${kcSanitize(msg("webauthn-login-title"))?no_esc}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-form-webauthn" class="${properties.kcFormClass!}">
|
||||
<form id="webauth" action="${url.loginAction}" method="post">
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||
<input type="hidden" id="authenticatorData" name="authenticatorData"/>
|
||||
<input type="hidden" id="signature" name="signature"/>
|
||||
<input type="hidden" id="credentialId" name="credentialId"/>
|
||||
<input type="hidden" id="userHandle" name="userHandle"/>
|
||||
<input type="hidden" id="error" name="error"/>
|
||||
</form>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} no-bottom-margin">
|
||||
<#if authenticators??>
|
||||
<form id="authn_select" class="${properties.kcFormClass!}">
|
||||
<#list authenticators.authenticators as authenticator>
|
||||
<input type="hidden" name="authn_use_chk" value="${authenticator.credentialId}"/>
|
||||
</#list>
|
||||
</form>
|
||||
|
||||
<#if shouldDisplayAuthenticators?? && shouldDisplayAuthenticators>
|
||||
<#if authenticators.authenticators?size gt 1>
|
||||
<p class="${properties.kcSelectAuthListItemTitle!}">${kcSanitize(msg("webauthn-available-authenticators"))?no_esc}</p>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcFormClass!}">
|
||||
<#list authenticators.authenticators as authenticator>
|
||||
<div id="kc-webauthn-authenticator" class="${properties.kcSelectAuthListItemClass!}">
|
||||
<div class="${properties.kcSelectAuthListItemIconClass!}">
|
||||
<i class="${(properties['${authenticator.transports.iconClass}'])!'${properties.kcWebAuthnDefaultIcon!}'} ${properties.kcSelectAuthListItemIconPropertyClass!}"></i>
|
||||
</div>
|
||||
<div class="${properties.kcSelectAuthListItemBodyClass!}">
|
||||
<div id="kc-webauthn-authenticator-label"
|
||||
class="${properties.kcSelectAuthListItemHeadingClass!}">
|
||||
${kcSanitize(msg('${authenticator.label}'))?no_esc}
|
||||
</div>
|
||||
|
||||
<#if authenticator.transports?? && authenticator.transports.displayNameProperties?has_content>
|
||||
<div id="kc-webauthn-authenticator-transport"
|
||||
class="${properties.kcSelectAuthListItemDescriptionClass!}">
|
||||
<#list authenticator.transports.displayNameProperties as nameProperty>
|
||||
<span>${kcSanitize(msg('${nameProperty!}'))?no_esc}</span>
|
||||
<#if nameProperty?has_next>
|
||||
<span>, </span>
|
||||
</#if>
|
||||
</#list>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="${properties.kcSelectAuthListItemDescriptionClass!}">
|
||||
<span id="kc-webauthn-authenticator-created-label">
|
||||
${kcSanitize(msg('webauthn-createdAt-label'))?no_esc}
|
||||
</span>
|
||||
<span id="kc-webauthn-authenticator-created">
|
||||
${kcSanitize(authenticator.createdAt)?no_esc}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="${properties.kcSelectAuthListItemFillClass!}"></div>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<input id="authenticateWebAuthnButton" type="button" onclick="webAuthnAuthenticate()" autofocus="autofocus"
|
||||
value="${kcSanitize(msg("webauthn-doAuthenticate"))}"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${url.resourcesPath}/js/base64url.js"></script>
|
||||
<script type="text/javascript">
|
||||
function webAuthnAuthenticate() {
|
||||
let isUserIdentified = ${isUserIdentified};
|
||||
if (!isUserIdentified) {
|
||||
doAuthenticate([]);
|
||||
return;
|
||||
}
|
||||
checkAllowCredentials();
|
||||
}
|
||||
|
||||
function checkAllowCredentials() {
|
||||
let allowCredentials = [];
|
||||
let authn_use = document.forms['authn_select'].authn_use_chk;
|
||||
|
||||
if (authn_use !== undefined) {
|
||||
if (authn_use.length === undefined) {
|
||||
allowCredentials.push({
|
||||
id: base64url.decode(authn_use.value, {loose: true}),
|
||||
type: 'public-key',
|
||||
});
|
||||
} else {
|
||||
for (let i = 0; i < authn_use.length; i++) {
|
||||
allowCredentials.push({
|
||||
id: base64url.decode(authn_use[i].value, {loose: true}),
|
||||
type: 'public-key',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
doAuthenticate(allowCredentials);
|
||||
}
|
||||
|
||||
|
||||
function doAuthenticate(allowCredentials) {
|
||||
|
||||
// Check if WebAuthn is supported by this browser
|
||||
if (!window.PublicKeyCredential) {
|
||||
$("#error").val("${msg("webauthn-unsupported-browser-text")?no_esc}");
|
||||
$("#webauth").submit();
|
||||
return;
|
||||
}
|
||||
|
||||
let challenge = "${challenge}";
|
||||
let userVerification = "${userVerification}";
|
||||
let rpId = "${rpId}";
|
||||
let publicKey = {
|
||||
rpId : rpId,
|
||||
challenge: base64url.decode(challenge, { loose: true })
|
||||
};
|
||||
|
||||
let createTimeout = ${createTimeout};
|
||||
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
|
||||
|
||||
if (allowCredentials.length) {
|
||||
publicKey.allowCredentials = allowCredentials;
|
||||
}
|
||||
|
||||
if (userVerification !== 'not specified') publicKey.userVerification = userVerification;
|
||||
|
||||
navigator.credentials.get({publicKey})
|
||||
.then((result) => {
|
||||
window.result = result;
|
||||
|
||||
let clientDataJSON = result.response.clientDataJSON;
|
||||
let authenticatorData = result.response.authenticatorData;
|
||||
let signature = result.response.signature;
|
||||
|
||||
$("#clientDataJSON").val(base64url.encode(new Uint8Array(clientDataJSON), { pad: false }));
|
||||
$("#authenticatorData").val(base64url.encode(new Uint8Array(authenticatorData), { pad: false }));
|
||||
$("#signature").val(base64url.encode(new Uint8Array(signature), { pad: false }));
|
||||
$("#credentialId").val(result.id);
|
||||
if(result.response.userHandle) {
|
||||
$("#userHandle").val(base64url.encode(new Uint8Array(result.response.userHandle), { pad: false }));
|
||||
}
|
||||
$("#webauth").submit();
|
||||
})
|
||||
.catch((err) => {
|
||||
$("#error").val(err);
|
||||
$("#webauth").submit();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
</script>
|
||||
<#elseif section = "info">
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,36 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayMessage=true; section>
|
||||
<#if section = "header">
|
||||
${kcSanitize(msg("webauthn-error-title"))?no_esc}
|
||||
<#elseif section = "form">
|
||||
|
||||
<script type="text/javascript">
|
||||
refreshPage = () => {
|
||||
document.getElementById('isSetRetry').value = 'retry';
|
||||
document.getElementById('executionValue').value = '${execution}';
|
||||
document.getElementById('kc-error-credential-form').submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form id="kc-error-credential-form" class="${properties.kcFormClass!}" action="${url.loginAction}"
|
||||
method="post">
|
||||
<input type="hidden" id="executionValue" name="authenticationExecution"/>
|
||||
<input type="hidden" id="isSetRetry" name="isSetRetry"/>
|
||||
</form>
|
||||
|
||||
<input tabindex="4" onclick="refreshPage()" type="button"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
name="try-again" id="kc-try-again" value="${kcSanitize(msg("doTryAgain"))?no_esc}"
|
||||
/>
|
||||
|
||||
<#if isAppInitiatedAction??>
|
||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-webauthn-settings-form" method="post">
|
||||
<button type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="cancelWebAuthnAIA" name="cancel-aia" value="true">${msg("doCancel")}
|
||||
</button>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,194 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
title
|
||||
<#elseif section = "header">
|
||||
${kcSanitize(msg("webauthn-registration-title"))?no_esc}
|
||||
<#elseif section = "form">
|
||||
|
||||
<form id="register" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||
<input type="hidden" id="attestationObject" name="attestationObject"/>
|
||||
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId"/>
|
||||
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel"/>
|
||||
<input type="hidden" id="transports" name="transports"/>
|
||||
<input type="hidden" id="error" name="error"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript" src="${url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="${url.resourcesPath}/js/base64url.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
function registerSecurityKey() {
|
||||
|
||||
// Check if WebAuthn is supported by this browser
|
||||
if (!window.PublicKeyCredential) {
|
||||
$("#error").val("${msg("webauthn-unsupported-browser-text")?no_esc}");
|
||||
$("#register").submit();
|
||||
return;
|
||||
}
|
||||
|
||||
// mandatory parameters
|
||||
let challenge = "${challenge}";
|
||||
let userid = "${userid}";
|
||||
let username = "${username}";
|
||||
|
||||
let signatureAlgorithms = "${signatureAlgorithms}";
|
||||
let pubKeyCredParams = getPubKeyCredParams(signatureAlgorithms);
|
||||
|
||||
let rpEntityName = "${rpEntityName}";
|
||||
let rp = {name: rpEntityName};
|
||||
|
||||
let publicKey = {
|
||||
challenge: base64url.decode(challenge, {loose: true}),
|
||||
rp: rp,
|
||||
user: {
|
||||
id: base64url.decode(userid, {loose: true}),
|
||||
name: username,
|
||||
displayName: username
|
||||
},
|
||||
pubKeyCredParams: pubKeyCredParams,
|
||||
};
|
||||
|
||||
// optional parameters
|
||||
let rpId = "${rpId}";
|
||||
publicKey.rp.id = rpId;
|
||||
|
||||
let attestationConveyancePreference = "${attestationConveyancePreference}";
|
||||
if (attestationConveyancePreference !== 'not specified') publicKey.attestation = attestationConveyancePreference;
|
||||
|
||||
let authenticatorSelection = {};
|
||||
let isAuthenticatorSelectionSpecified = false;
|
||||
|
||||
let authenticatorAttachment = "${authenticatorAttachment}";
|
||||
if (authenticatorAttachment !== 'not specified') {
|
||||
authenticatorSelection.authenticatorAttachment = authenticatorAttachment;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
let requireResidentKey = "${requireResidentKey}";
|
||||
if (requireResidentKey !== 'not specified') {
|
||||
if (requireResidentKey === 'Yes')
|
||||
authenticatorSelection.requireResidentKey = true;
|
||||
else
|
||||
authenticatorSelection.requireResidentKey = false;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
let userVerificationRequirement = "${userVerificationRequirement}";
|
||||
if (userVerificationRequirement !== 'not specified') {
|
||||
authenticatorSelection.userVerification = userVerificationRequirement;
|
||||
isAuthenticatorSelectionSpecified = true;
|
||||
}
|
||||
|
||||
if (isAuthenticatorSelectionSpecified) publicKey.authenticatorSelection = authenticatorSelection;
|
||||
|
||||
let createTimeout = ${createTimeout};
|
||||
if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000;
|
||||
|
||||
let excludeCredentialIds = "${excludeCredentialIds}";
|
||||
let excludeCredentials = getExcludeCredentials(excludeCredentialIds);
|
||||
if (excludeCredentials.length > 0) publicKey.excludeCredentials = excludeCredentials;
|
||||
|
||||
navigator.credentials.create({publicKey})
|
||||
.then(function (result) {
|
||||
window.result = result;
|
||||
let clientDataJSON = result.response.clientDataJSON;
|
||||
let attestationObject = result.response.attestationObject;
|
||||
let publicKeyCredentialId = result.rawId;
|
||||
|
||||
$("#clientDataJSON").val(base64url.encode(new Uint8Array(clientDataJSON), {pad: false}));
|
||||
$("#attestationObject").val(base64url.encode(new Uint8Array(attestationObject), {pad: false}));
|
||||
$("#publicKeyCredentialId").val(base64url.encode(new Uint8Array(publicKeyCredentialId), {pad: false}));
|
||||
|
||||
if (typeof result.response.getTransports === "function") {
|
||||
let transports = result.response.getTransports();
|
||||
if (transports) {
|
||||
$("#transports").val(getTransportsAsString(transports));
|
||||
}
|
||||
} else {
|
||||
console.log("Your browser is not able to recognize supported transport media for the authenticator.");
|
||||
}
|
||||
|
||||
let initLabel = "WebAuthn Authenticator (Default Label)";
|
||||
let labelResult = $("#auth-name").val();
|
||||
if (labelResult === null || labelResult === "") labelResult = initLabel;
|
||||
$("#authenticatorLabel").val(labelResult);
|
||||
|
||||
$("#register").submit();
|
||||
|
||||
})
|
||||
.catch(function (err) {
|
||||
$("#error").val(err);
|
||||
$("#register").submit();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function getPubKeyCredParams(signatureAlgorithms) {
|
||||
let pubKeyCredParams = [];
|
||||
if (signatureAlgorithms === "") {
|
||||
pubKeyCredParams.push({type: "public-key", alg: -7});
|
||||
return pubKeyCredParams;
|
||||
}
|
||||
let signatureAlgorithmsList = signatureAlgorithms.split(',');
|
||||
|
||||
for (let i = 0; i < signatureAlgorithmsList.length; i++) {
|
||||
pubKeyCredParams.push({
|
||||
type: "public-key",
|
||||
alg: signatureAlgorithmsList[i]
|
||||
});
|
||||
}
|
||||
return pubKeyCredParams;
|
||||
}
|
||||
|
||||
function getExcludeCredentials(excludeCredentialIds) {
|
||||
let excludeCredentials = [];
|
||||
if (excludeCredentialIds === "") return excludeCredentials;
|
||||
|
||||
let excludeCredentialIdsList = excludeCredentialIds.split(',');
|
||||
|
||||
for (let i = 0; i < excludeCredentialIdsList.length; i++) {
|
||||
excludeCredentials.push({
|
||||
type: "public-key",
|
||||
id: base64url.decode(excludeCredentialIdsList[i],
|
||||
{loose: true})
|
||||
});
|
||||
}
|
||||
return excludeCredentials;
|
||||
}
|
||||
|
||||
function getTransportsAsString(transportsList) {
|
||||
if (transportsList === '' || transportsList.constructor !== Array) return "";
|
||||
|
||||
let transportsString = "";
|
||||
|
||||
for (let i = 0; i < transportsList.length; i++) {
|
||||
transportsString += transportsList[i] + ",";
|
||||
}
|
||||
|
||||
return transportsString.slice(0, -1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<label for="auth-name">Device Name:</label> <input type="text"
|
||||
id="auth-name" placeholder="WebAuthn Authenticator"/>
|
||||
|
||||
<input type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="registerWebAuthn" value="${msg("doRegister")}" onclick="registerSecurityKey()"/>
|
||||
|
||||
<#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
|
||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-webauthn-settings-form"
|
||||
method="post">
|
||||
<button type="submit"
|
||||
class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
|
||||
id="cancelWebAuthnAIA" name="cancel-aia" value="true">${msg("doCancel")}
|
||||
</button>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
Loading…
Reference in New Issue