KEYCLOAK-13565 Add support for kc_action to keycloak.js
Co-authored-by mhajas <mhajas@redhat.com>
This commit is contained in:
parent
97b5654690
commit
1f02f87a6e
6 changed files with 70 additions and 6 deletions
|
@ -173,7 +173,7 @@ declare namespace Keycloak {
|
||||||
* If value is `'register'` then user is redirected to registration page,
|
* If value is `'register'` then user is redirected to registration page,
|
||||||
* otherwise to login page.
|
* otherwise to login page.
|
||||||
*/
|
*/
|
||||||
action?: 'register';
|
action?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used just if user is already authenticated. Specifies maximum time since
|
* Used just if user is already authenticated. Specifies maximum time since
|
||||||
|
@ -433,6 +433,11 @@ declare namespace Keycloak {
|
||||||
*/
|
*/
|
||||||
onTokenExpired?(): void;
|
onTokenExpired?(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a AIA has been requested by the application.
|
||||||
|
*/
|
||||||
|
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to initialize the adapter.
|
* Called to initialize the adapter.
|
||||||
* @param initOptions Initialization options.
|
* @param initOptions Initialization options.
|
||||||
|
|
|
@ -476,6 +476,10 @@
|
||||||
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.action && options.action != 'register') {
|
||||||
|
url += '&kc_action=' + encodeURIComponent(options.action);
|
||||||
|
}
|
||||||
|
|
||||||
if (options && options.locale) {
|
if (options && options.locale) {
|
||||||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||||
}
|
}
|
||||||
|
@ -740,6 +744,10 @@
|
||||||
|
|
||||||
var timeLocal = new Date().getTime();
|
var timeLocal = new Date().getTime();
|
||||||
|
|
||||||
|
if (oauth['kc_action_status']) {
|
||||||
|
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (prompt != 'none') {
|
if (prompt != 'none') {
|
||||||
var errorData = { error: error, error_description: oauth.error_description };
|
var errorData = { error: error, error_description: oauth.error_description };
|
||||||
|
@ -1085,13 +1093,13 @@
|
||||||
var supportedParams;
|
var supportedParams;
|
||||||
switch (kc.flow) {
|
switch (kc.flow) {
|
||||||
case 'standard':
|
case 'standard':
|
||||||
supportedParams = ['code', 'state', 'session_state'];
|
supportedParams = ['code', 'state', 'session_state', 'kc_action_status'];
|
||||||
break;
|
break;
|
||||||
case 'implicit':
|
case 'implicit':
|
||||||
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in'];
|
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status'];
|
||||||
break;
|
break;
|
||||||
case 'hybrid':
|
case 'hybrid':
|
||||||
supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state'];
|
supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state', 'kc_action_status'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button onclick="keycloak.login()">Login</button>
|
<button onclick="keycloak.login()">Login</button>
|
||||||
|
<button onclick="keycloak.login({ action: 'UPDATE_PASSWORD' })">Update Password</button>
|
||||||
<button onclick="keycloak.logout()">Logout</button>
|
<button onclick="keycloak.logout()">Logout</button>
|
||||||
<button onclick="keycloak.register()">Register</button>
|
<button onclick="keycloak.register()">Register</button>
|
||||||
<button onclick="keycloak.accountManagement()">Account</button>
|
<button onclick="keycloak.accountManagement()">Account</button>
|
||||||
|
@ -155,6 +156,17 @@
|
||||||
event('Access token expired.');
|
event('Access token expired.');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
keycloak.onActionUpdate = function (status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'success':
|
||||||
|
event('Action completed successfully'); break;
|
||||||
|
case 'cancelled':
|
||||||
|
event('Action cancelled by user'); break;
|
||||||
|
case 'error':
|
||||||
|
event('Action failed'); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
|
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
|
||||||
var initOptions = {
|
var initOptions = {
|
||||||
responseMode: 'fragment',
|
responseMode: 'fragment',
|
||||||
|
|
|
@ -114,6 +114,7 @@ public class JavascriptTestExecutor {
|
||||||
jsExecutor.executeScript("window.keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}");
|
jsExecutor.executeScript("window.keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}");
|
||||||
jsExecutor.executeScript("window.keycloak.onAuthLogout = function () {event('Auth Logout')}");
|
jsExecutor.executeScript("window.keycloak.onAuthLogout = function () {event('Auth Logout')}");
|
||||||
jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}");
|
jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}");
|
||||||
|
jsExecutor.executeScript("window.keycloak.onActionUpdate = function (status) {event('AIA status: ' + status)}");
|
||||||
|
|
||||||
configured = true;
|
configured = true;
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
|
||||||
public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript";
|
public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript";
|
||||||
public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
|
public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
|
||||||
public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
|
public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
|
||||||
|
public static final String USER_PASSWORD = "password";
|
||||||
|
|
||||||
|
|
||||||
protected JavascriptExecutor jsExecutor;
|
protected JavascriptExecutor jsExecutor;
|
||||||
|
@ -80,8 +81,8 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
|
||||||
public static final UserRepresentation unauthorizedUser;
|
public static final UserRepresentation unauthorizedUser;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
testUser = UserBuilder.create().username("test-user@localhost").password("password").build();
|
testUser = UserBuilder.create().username("test-user@localhost").password(USER_PASSWORD).build();
|
||||||
unauthorizedUser = UserBuilder.create().username("unauthorized").password("password").build();
|
unauthorizedUser = UserBuilder.create().username("unauthorized").password(USER_PASSWORD).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.testsuite.ProfileAssume;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.auth.page.account.Applications;
|
import org.keycloak.testsuite.auth.page.account.Applications;
|
||||||
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
|
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
|
||||||
|
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
|
||||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
import org.keycloak.testsuite.util.JavascriptBrowser;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
@ -77,6 +78,10 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
@JavascriptBrowser
|
@JavascriptBrowser
|
||||||
private OAuthGrant oAuthGrantPage;
|
private OAuthGrant oAuthGrantPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
@JavascriptBrowser
|
||||||
|
private UpdatePassword updatePasswordPage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RealmRepresentation updateRealm(RealmBuilder builder) {
|
protected RealmRepresentation updateRealm(RealmBuilder builder) {
|
||||||
return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
|
return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
|
||||||
|
@ -660,4 +665,36 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
||||||
.executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Success handle"));
|
.executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Success handle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAIAFromJavascriptAdapterSuccess() {
|
||||||
|
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
|
||||||
|
.login(JSObjectBuilder.create()
|
||||||
|
.add("action", "UPDATE_PASSWORD")
|
||||||
|
.build(), this::assertOnLoginPage)
|
||||||
|
.loginForm(testUser);
|
||||||
|
|
||||||
|
updatePasswordPage.updatePasswords(USER_PASSWORD, USER_PASSWORD);
|
||||||
|
|
||||||
|
testExecutor.init(defaultArguments(), (driver1, output, events1) -> {
|
||||||
|
assertSuccessfullyLoggedIn(driver1, output, events1);
|
||||||
|
waitUntilElement(events1).text().contains("AIA status: success");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAIAFromJavascriptAdapterCancelled() {
|
||||||
|
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
|
||||||
|
.login(JSObjectBuilder.create()
|
||||||
|
.add("action", "UPDATE_PASSWORD")
|
||||||
|
.build(), this::assertOnLoginPage)
|
||||||
|
.loginForm(testUser);
|
||||||
|
|
||||||
|
updatePasswordPage.cancel();
|
||||||
|
|
||||||
|
testExecutor.init(defaultArguments(), (driver1, output, events1) -> {
|
||||||
|
assertSuccessfullyLoggedIn(driver1, output, events1);
|
||||||
|
waitUntilElement(events1).text().contains("AIA status: cancelled");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue