diff --git a/README.md b/README.md index ae93250..6a784e7 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,11 @@ yarn add react-native-keycloak-plugin ### App configuration -Please configure [Linking](https://facebook.github.io/react-native/docs/linking.html) module, including steps for handling Universal links (This might get changed due to not being able to close the tab on leave, ending up with a lot of tabs in the browser). +Please configure [Linking](https://facebook.github.io/react-native/docs/linking.html) module, including steps for handling Universal links
+This might get changed due to not being able to close the tab on leave, ending up with a lot of tabs in the browser.
+_[Not needed if you're using React Native >= 0.60]_ -Also, add the applinks: entry to the Associated Domains Capability of your app. +Also, add the applinks: `` entry to the Associated Domains Capability of your app. ### Imports @@ -43,7 +45,7 @@ From that variable, you have access to all the util methods the plugin implement ### Keycloak.login ```js -Keycloak.login(conf, callback, scope) +Keycloak.keycloakUILogin(conf, callback, scope) .then((response) => /* Your resolve */ ) .catch((error) => /* Your reject*/ ) ``` @@ -88,7 +90,7 @@ response.tokens = { ### Keycloak.apiLogin ```js -Keycloak.apiLogin(conf, username, password, [scope = 'info']) +Keycloak.login(conf, username, password, [scope = 'info']) .then((response) => /* Your resolve */ ) .catch((error) => /* Your reject*/ ) ``` @@ -98,6 +100,18 @@ Method arguments: - _username_: The username to be logged in - _password_: The password associated to the above username - _scope_: same behavior as above + +```js +Keycloak.refreshLogin([scope = 'info']) + .then((response) => /* Your resolve */ ) + .catch((error) => /* Your reject*/ ) +``` + +Method arguments: + - _scope_: same behavior as above + +Sometimes you may need to re-login your user w/ Keycloak via the login process but, for some reason, you don't want / can't display the login page.
+This method will re-login your user by recycling the last combination of username/password he entered, reading them from the AsyncStorage. #### Manually handling the tokens @@ -105,7 +119,7 @@ Method arguments: import Keycloak, { TokenStorage } from 'react-native-keycloak-plugin' ``` -Logging in by the login function will save the tokens information and the configuration object into the AsyncStorage.
Through the TokenStorage object, the plugin exports some methods that can be used to interact with these objects. +Logging in by the login function will save the tokens information, and the configuration object into the AsyncStorage.
Through the TokenStorage object, the plugin exports some methods that can be used to interact with these objects. ### Keycloak.retrieveUserInfo ```js diff --git a/package.json b/package.json index af6d976..07563cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-keycloak-plugin", - "version": "0.5.1", + "version": "1.0.0", "description": "Functional React Native module for authentication between a client and the keycloak server.", "main": "src/index.js", "scripts": { diff --git a/src/Constants.js b/src/Constants.js index 27c95c9..fcb537a 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -1,7 +1,8 @@ +const CONFIG = '@keyCloakConfig'; +const CREDENTIALS = '@credentials'; const GET = 'GET'; const POST = 'POST'; -const URL = 'url'; const TOKENS = '@tokens'; -const CONFIG = '@keyCloakConfig'; +const URL = 'url'; -export { CONFIG, GET, POST, TOKENS, URL }; +export { CONFIG, CREDENTIALS, GET, POST, TOKENS, URL }; diff --git a/src/Core.js b/src/Core.js index 204b811..a77b8c1 100644 --- a/src/Core.js +++ b/src/Core.js @@ -59,24 +59,10 @@ const retrieveTokens = async (conf, code, resolve, reject, deepLinkUrl) => { } }; - -// ### PUBLIC METHODS - -export const login = (conf, callback, scope = 'info') => new Promise(((resolve, reject) => { - const { url, state } = getLoginURL(conf, scope); - - const listener = event => onOpenURL(conf, resolve, reject, state, event, retrieveTokens); - Linking.addEventListener(URL, listener); - - const doLogin = callback || Linking.openURL; - doLogin(url); -})); - -export const apiLogin = async (conf, username, password, scope = 'info') => { +const performLogin = async (conf, username, password, scope = 'info') => { const { resource, realm, credentials, 'auth-server-url': authServerUrl, } = conf; - const url = `${getRealmURL(realm, authServerUrl)}/protocol/openid-connect/token`; const method = POST; const body = qs.stringify({ @@ -95,18 +81,56 @@ export const apiLogin = async (conf, username, password, scope = 'info') => { if (fullResponse.status === 200) { await TokenStorage.saveConfiguration(conf); await TokenStorage.saveTokens(jsonResponse); + await TokenStorage.saveCredentials({ username, password }); return jsonResponse; } console.error(`Error during kc-api-login, ${fullResponse.status}: ${jsonResponse.url}`); - return Promise.reject(jsonResponse.error_description); + return Promise.reject(); +}; + + +// ### PUBLIC METHODS + +export const keycloakUILogin = (conf, callback, scope = 'info') => new Promise(((resolve, reject) => { + const { url, state } = getLoginURL(conf, scope); + + const listener = event => onOpenURL(conf, resolve, reject, state, event, retrieveTokens); + Linking.addEventListener(URL, listener); + + const doLogin = callback || Linking.openURL; + doLogin(url); +})); + +export const login = async (conf, username, password, scope = 'info') => performLogin(conf, username, password, scope); + +export const refreshLogin = async (scope = 'info') => { + const conf = await TokenStorage.getConfiguration(); + if (!conf) { + console.error('Error during kc-refresh-login: Could not read configuration from storage'); + return Promise.reject(); + } + + const credentials = await TokenStorage.getCredentials(); + if (!credentials) { + console.error('Error during kc-refresh-login: Could not read from AsyncStorage'); + return Promise.reject(); + } + const { username, password } = credentials; + if (!username || !password) { + console.error('Error during kc-refresh-login: Username or Password not found'); + return Promise.reject(); + } + + return performLogin(conf, scope, username, password); }; export const retrieveUserInfo = async () => { const conf = await TokenStorage.getConfiguration(); if (!conf) { - return Promise.reject(Error('Could not read configuration from storage')); + console.error('Error during kc-retrieve-user-info: Could not read configuration from storage'); + return Promise.reject(); } const { realm, 'auth-server-url': authServerUrl } = conf; @@ -129,14 +153,15 @@ export const retrieveUserInfo = async () => { } console.error(`Error during kc-retrieve-user-info: ${fullResponse.status}: ${fullResponse.url}`); - return Promise.reject(jsonResponse.error_description); + return Promise.reject(); }; export const refreshToken = async () => { const conf = await TokenStorage.getConfiguration(); if (!conf) { - return Promise.reject(Error('Could not read configuration from storage')); + console.error('Could not read configuration from storage'); + return Promise.reject(); } const { @@ -168,21 +193,23 @@ export const refreshToken = async () => { } console.error(`Error during kc-refresh-token, ${fullResponse.status}: ${fullResponse.url}`); - return Promise.reject(jsonResponse.error_description); + return Promise.reject(jsonResponse); }; export const logout = async () => { const conf = await TokenStorage.getConfiguration(); if (!conf) { - return Promise.reject(Error('Could not read configuration from storage')); + console.error('Could not read configuration from storage'); + return Promise.reject(); } const { realm, 'auth-server-url': authServerUrl } = conf; const savedTokens = await TokenStorage.getTokens(); if (!savedTokens) { - return Promise.reject(new Error(`Error during kc-logout, savedTokens is ${savedTokens}`)); + console.error(`Error during kc-logout, savedTokens is ${savedTokens}`); + return Promise.reject(); } const logoutUrl = `${getRealmURL(realm, authServerUrl)}/protocol/openid-connect/logout`; @@ -195,7 +222,6 @@ export const logout = async () => { return Promise.resolve(); } - const jsonResponse = await fullResponse.json(); console.error(`Error during kc-logout: ${fullResponse.status}: ${fullResponse.url}`); - return Promise.reject(jsonResponse.error_description); + return Promise.reject(); }; diff --git a/src/TokenStorage.js b/src/TokenStorage.js index 56791ba..46c58b0 100644 --- a/src/TokenStorage.js +++ b/src/TokenStorage.js @@ -1,8 +1,12 @@ import AsyncStorage from '@react-native-community/async-storage'; -import { TOKENS as TOKENS_KEY, CONFIG as CONFIG_KEY } from './Constants'; +import { TOKENS as TOKENS_KEY, CONFIG as CONFIG_KEY, CREDENTIALS } from './Constants'; const TokenStorage = { + saveCredentials: async (credentials) => { + await AsyncStorage.setItem(CREDENTIALS, JSON.stringify(credentials)); + }, + saveConfiguration: async (conf) => { await AsyncStorage.setItem(CONFIG_KEY, JSON.stringify(conf)); }, @@ -11,6 +15,11 @@ const TokenStorage = { await AsyncStorage.setItem(TOKENS_KEY, JSON.stringify(tokens)); }, + getCredentials: async () => { + const credentials = await AsyncStorage.getItem(CREDENTIALS); + return (credentials) ? JSON.parse(credentials) : undefined; + }, + getConfiguration: async () => { const conf = await AsyncStorage.getItem(CONFIG_KEY); return (conf) ? JSON.parse(conf) : undefined; diff --git a/src/index.js b/src/index.js index 62e83f2..a5bd010 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,13 @@ -import { apiLogin, login, logout, refreshToken, retrieveUserInfo } from './Core'; +import { keycloakUILogin, login, logout, refreshLogin, refreshToken, retrieveUserInfo } from './Core'; export { default as TokenStorage } from './TokenStorage'; export { TokensUtils } from './Utils'; export default { - apiLogin, + keycloakUILogin, login, logout, + refreshLogin, refreshToken, retrieveUserInfo, };