Add UNIX.dog theme, no-JS account console

This commit is contained in:
Citlali del Rey 2023-04-07 12:51:46 -07:00
parent 1ee98bbbe7
commit 5d9b49b47d
Signed by: nullobsi
GPG Key ID: 933A1F44222C2634
67 changed files with 5348 additions and 183 deletions

View File

@ -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;
}

View File

@ -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));

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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) {

View File

@ -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);

View File

@ -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.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.TOTP);
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);
}
}

View File

@ -8,5 +8,8 @@
}, {
"name" : "keycloak.v2",
"types": [ "account", "admin" ]
}, {
"name" : "unixdog",
"types": [ "account", "login" ]
}]
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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=&laquo; 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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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));

View File

@ -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>

View File

@ -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>
&#x1F12F; <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>

View File

@ -0,0 +1,4 @@
styles=css/main.css
kcInputErrorMessageClass=error

View File

@ -0,0 +1,7 @@
_ __ _ _
| |/ /___ _ _ ___| | ___ __ _| | __
| ' // _ \ | | |/ __| |/ _ \ / _` | |/ /
| . \ __/ |_| | (__| | (_) | (_| | <
|_|\_\___|\__, |\___|_|\___/ \__,_|_|\_\
|___/

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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=&laquo; 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=&laquo; 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=&raquo; 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.

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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));

View File

@ -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>

View File

@ -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>

View File

@ -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>
&#x1F12F; <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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
styles=css/main.css
kcInputErrorMessageClass=error

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>