diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/context/QContext.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/context/QContext.java index 4c7bcbba..80515a5e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/context/QContext.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/context/QContext.java @@ -84,7 +84,7 @@ public class QContext actionStackThreadLocal.get().add(actionInput); } - if(!qInstance.getHasBeenValidated()) + if(qInstance != null && !qInstance.getHasBeenValidated()) { try { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/Auth0AuthenticationMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/Auth0AuthenticationMetaData.java index c0bc5e09..1e8f341d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/Auth0AuthenticationMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/Auth0AuthenticationMetaData.java @@ -60,7 +60,6 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData private String auth0ClientSecretField; private Serializable qqqRecordIdField; - ///////////////////////////////////// // fields on the accessToken table // ///////////////////////////////////// @@ -70,6 +69,14 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData private String qqqApiKeyField; private String expiresInSecondsField; + ///////////////////////////////////////////////////////////////////////////////// + // table for storing user sessions, and field names we work with on that table // + ///////////////////////////////////////////////////////////////////////////////// + private String userSessionTableName; + private String userSessionUuidField; + private String userSessionUserIdField; + private String userSessionAccessTokenField; + /******************************************************************************* diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java index 315cff73..e9f3b732 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModule.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import com.auth0.client.auth.AuthAPI; import com.auth0.exception.Auth0Exception; import com.auth0.json.auth.TokenHolder; @@ -52,6 +51,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +import com.kingsrook.qqq.backend.core.context.CapturedContext; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException; import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException; @@ -68,11 +68,11 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType; import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QUser; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.model.UserSession; import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider; import com.kingsrook.qqq.backend.core.state.SimpleStateKey; import com.kingsrook.qqq.backend.core.state.StateProviderInterface; @@ -80,7 +80,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; -import org.apache.http.HttpStatus; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -93,6 +92,18 @@ import org.json.JSONObject; /******************************************************************************* + ** QQQ AuthenticationModule for working with Auth0. + ** + ** createSession can be called with the following fields in its context: + ** + ** System-User session use-case: + ** 1: Takes in an "accessToken" (but doesn't store a userSession record). + ** + ** Web User session use-cases: + ** 2: creates a new session (userSession record) by taking an "accessToken" + ** 3: looks up an existing session (userSession record) by taking a "sessionUUID" + ** 4: takes an "apiKey" (looked up in metaData.AccessTokenTableName - refreshing accessToken with auth0 if needed). + ** 5: takes a "basicAuthString" (encoded username:password), which make a new accessToken in auth0 ** *******************************************************************************/ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface @@ -104,14 +115,17 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface ///////////////////////////////////////////////////////////////////////////////////////////////////////////// public static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 1800; - public static final String AUTH0_ACCESS_TOKEN_KEY = "sessionId"; - public static final String API_KEY = "apiKey"; - public static final String BASIC_AUTH_KEY = "basicAuthString"; + public static final String ACCESS_TOKEN_KEY = "accessToken"; + public static final String API_KEY = "apiKey"; // todo - look for users of this, see if we can change to use this constant; maybe move constants up? + public static final String SESSION_UUID_KEY = "sessionUUID"; + public static final String BASIC_AUTH_KEY = "basicAuthString"; // todo - look for users of this, see if we can change to use this constant; maybe move constants up? - public static final String TOKEN_NOT_PROVIDED_ERROR = "Access Token was not provided"; - public static final String COULD_NOT_DECODE_ERROR = "Unable to decode access token"; - public static final String EXPIRED_TOKEN_ERROR = "Token has expired"; - public static final String INVALID_TOKEN_ERROR = "An invalid token was provided"; + public static final String DO_STORE_USER_SESSION_KEY = "doStoreUserSession"; + + static final String TOKEN_NOT_PROVIDED_ERROR = "Access Token was not provided"; + static final String COULD_NOT_DECODE_ERROR = "Unable to decode access token"; + static final String EXPIRED_TOKEN_ERROR = "Token has expired"; + static final String INVALID_TOKEN_ERROR = "An invalid token was provided"; //////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -149,94 +163,105 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface @Override public QSession createSession(QInstance qInstance, Map context) throws QAuthenticationException { - Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); - - /////////////////////////////////////////////////////////// - // check if we are processing a Basic Auth Session first // - /////////////////////////////////////////////////////////// - if(context.containsKey(BASIC_AUTH_KEY)) - { - AuthAPI auth = AuthAPI.newBuilder(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret()).build(); - try - { - ///////////////////////////////////////////////// - // decode the credentials from the header auth // - ///////////////////////////////////////////////// - String base64Credentials = context.get(BASIC_AUTH_KEY).trim(); - String accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials); - context.put(AUTH0_ACCESS_TOKEN_KEY, accessToken); - } - catch(Auth0Exception e) - { - //////////////// - // ¯\_(ツ)_/¯ // - //////////////// - String message = "Error handling basic authentication: " + e.getMessage(); - LOG.error(message, e); - throw (new QAuthenticationException(message)); - } - } - - //////////////////////////////////////////////////////////////////// - // get the jwt id or qqq translated token from the context object // - //////////////////////////////////////////////////////////////////// - String accessToken = context.get(AUTH0_ACCESS_TOKEN_KEY); - - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // check to see if the session id is a UUID, if so, that means we need to look up the 'actual' token in the access_token table // - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - if(accessToken != null && StringUtils.isUUID(accessToken)) - { - accessToken = lookupActualAccessToken(metaData, accessToken); - } - - //////////////////////////////////////////////////////// - // if access token is still null, look for an api key // - //////////////////////////////////////////////////////// - if(accessToken == null) - { - String apiKey = context.get(API_KEY); - if(apiKey != null) - { - accessToken = getAccessTokenFromApiKey(metaData, apiKey); - } - } - - if(accessToken == null) - { - LOG.warn(TOKEN_NOT_PROVIDED_ERROR); - throw (new QAuthenticationException(TOKEN_NOT_PROVIDED_ERROR)); - } - - ////////////////////////////////////////////////////////////////////////////////////// - // decode the token locally to make sure it is valid and to look at when it expires // - ////////////////////////////////////////////////////////////////////////////////////// try { + Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); + + ///////////////////////////////////////////////////////////////////////////////////////////// + // if the context contains an access token, then create a new session based on that token. // + ///////////////////////////////////////////////////////////////////////////////////////////// + String accessToken = null; + if(CollectionUtils.containsKeyWithNonNullValue(context, ACCESS_TOKEN_KEY)) + { + accessToken = context.get(ACCESS_TOKEN_KEY); + QSession qSession = buildAndValidateSession(qInstance, accessToken); + + //////////////////////////////////////////////////////////////// + // build & store userSession db record, if requested to do so // + //////////////////////////////////////////////////////////////// + if(CollectionUtils.containsKeyWithNonNullValue(context, DO_STORE_USER_SESSION_KEY)) + { + insertUserSession(qInstance, accessToken, qSession); + } + + return (qSession); + } + else if(CollectionUtils.containsKeyWithNonNullValue(context, BASIC_AUTH_KEY)) + { + ////////////////////////////////////////////////////////////////////////////////////// + // Process a basic auth (username:password) // + // by getting an access token from auth0 (re-using from state provider if possible) // + ////////////////////////////////////////////////////////////////////////////////////// + AuthAPI auth = AuthAPI.newBuilder(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret()).build(); + try + { + ///////////////////////////////////////////////// + // decode the credentials from the header auth // + ///////////////////////////////////////////////// + String base64Credentials = context.get(BASIC_AUTH_KEY).trim(); + accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials); + } + catch(Auth0Exception e) + { + //////////////// + // ¯\_(ツ)_/¯ // + //////////////// + String message = "Error handling basic authentication: " + e.getMessage(); + LOG.error(message, e); + throw (new QAuthenticationException(message)); + } + } + else if(CollectionUtils.containsKeyWithNonNullValue(context, API_KEY)) + { + /////////////////////////////////////////////////////////////////////////////////////// + // process an api key - looks up client application token (creating token if needed) // + /////////////////////////////////////////////////////////////////////////////////////// + String apiKey = context.get(API_KEY); + if(apiKey != null) + { + accessToken = getAccessTokenFromApiKey(metaData, apiKey); + } + } + else if(CollectionUtils.containsKeyWithNonNullValue(context, SESSION_UUID_KEY)) + { + ///////////////////////////////////////////////////////////////////////////////////////// + // process a sessionUUID - looks up userSession record - cannot create token this way. // + ///////////////////////////////////////////////////////////////////////////////////////// + String sessionUUID = context.get(SESSION_UUID_KEY); + if(sessionUUID != null) + { + accessToken = getAccessTokenFromSessionUUID(metaData, sessionUUID); + } + } + + /* todo confirm this is deprecated + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // check to see if the session id is a UUID, if so, that means we need to look up the 'actual' token in the access_token table // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(accessToken != null && StringUtils.isUUID(accessToken)) + { + accessToken = lookupActualAccessToken(metaData, accessToken); + } + */ + + /////////////////////////////////////////// + // if token wasn't found by now, give up // + /////////////////////////////////////////// + if(accessToken == null) + { + LOG.warn(TOKEN_NOT_PROVIDED_ERROR); + throw (new QAuthenticationException(TOKEN_NOT_PROVIDED_ERROR)); + } + ///////////////////////////////////////////////////// // try to build session to see if still valid // // then call method to check more session validity // ///////////////////////////////////////////////////// - QSession qSession = buildQSessionFromToken(accessToken, qInstance); - if(isSessionValid(qInstance, qSession)) - { - return (qSession); - } - - /////////////////////////////////////////////////////////////////////////////////////// - // if we make it here it means we have never validated this token or its been a long // - // enough duration so we need to re-verify the token // - /////////////////////////////////////////////////////////////////////////////////////// - qSession = revalidateTokenAndBuildSession(qInstance, accessToken); - - //////////////////////////////////////////////////////////////////// - // put now into state so we dont check until next interval passes // - /////////////////////////////////////////////////////////////////// - StateProviderInterface spi = getStateProvider(); - SimpleStateKey key = new SimpleStateKey<>(qSession.getIdReference()); - spi.put(key, Instant.now()); - - return (qSession); + return buildAndValidateSession(qInstance, accessToken); + } + catch(QAuthenticationException qae) + { + throw (qae); } catch(JWTDecodeException jde) { @@ -272,6 +297,61 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface + /******************************************************************************* + ** Insert a session as a new record into userSession table + *******************************************************************************/ + private void insertUserSession(QInstance qInstance, String accessToken, QSession qSession) throws QException + { + CapturedContext capturedContext = QContext.capture(); + try + { + QContext.init(qInstance, null); + QContext.setQSession(getChickenAndEggSession()); + + UserSession userSession = new UserSession() + .withUuid(qSession.getUuid()) + .withUserId(qSession.getUser().getIdReference()) + .withAccessToken(accessToken); + + new InsertAction().execute(new InsertInput(UserSession.TABLE_NAME).withRecordEntity(userSession)); + } + finally + { + QContext.init(capturedContext); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private QSession buildAndValidateSession(QInstance qInstance, String accessToken) throws JwkException + { + QSession qSession = buildQSessionFromToken(accessToken, qInstance); + if(isSessionValid(qInstance, qSession)) + { + return (qSession); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // if we make it here it means we have never validated this token or it has been a long // + // enough duration so we need to re-verify the token // + ////////////////////////////////////////////////////////////////////////////////////////// + qSession = revalidateTokenAndBuildSession(qInstance, accessToken); + + ///////////////////////////////////////////////////////////////////// + // put now into state so we don't check until next interval passes // + ///////////////////////////////////////////////////////////////////// + StateProviderInterface spi = getStateProvider(); + SimpleStateKey key = new SimpleStateKey<>(qSession.getIdReference()); + spi.put(key, Instant.now()); + + return (qSession); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -299,7 +379,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); String credentials = new String(credDecoded, StandardCharsets.UTF_8); - String accessToken = getAccessTokenFromAuth0(metaData, auth, credentials); + String accessToken = getAccessTokenForUsernameAndPasswordFromAuth0(metaData, auth, credentials); stateProvider.put(accessTokenStateKey, accessToken); stateProvider.put(timestampStateKey, Instant.now()); return (accessToken); @@ -310,7 +390,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface /******************************************************************************* ** *******************************************************************************/ - protected String getAccessTokenFromAuth0(Auth0AuthenticationMetaData metaData, AuthAPI auth, String credentials) throws Auth0Exception + protected String getAccessTokenForUsernameAndPasswordFromAuth0(Auth0AuthenticationMetaData metaData, AuthAPI auth, String credentials) throws Auth0Exception { ///////////////////////////////////// // call auth0 with a login request // @@ -620,75 +700,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface /******************************************************************************* - ** create a new auth0 access token + ** make http request to Auth0 for a new access token for an application - e.g., + ** with a clientId and clientSecret as params ** *******************************************************************************/ - public String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException - { - QSession sessionBefore = QContext.getQSession(); - Auth0AuthenticationMetaData auth0MetaData = (Auth0AuthenticationMetaData) metaData; - - try - { - QContext.setQSession(getChickenAndEggSession()); - - /////////////////////////////////////////////////////////////////////////////////////// - // fetch the application from database, will throw accesstokenexception if not found // - /////////////////////////////////////////////////////////////////////////////////////// - QRecord clientAuth0Application = getClientAuth0Application(auth0MetaData, clientId); - - ///////////////////////////////////////////////////////////////////////////////////////////////// - // request access token from auth0 if exception is not thrown, that means 200OK, we want to // - // store the actual access token in the database, and return a unique value // - // back to the user which will be what they use on subseqeunt requests (because token too big) // - ///////////////////////////////////////////////////////////////////////////////////////////////// - JSONObject accessTokenData = requestAccessTokenFromAuth0(auth0MetaData, clientId, clientSecret); - - Integer expiresInSeconds = accessTokenData.getInt("expires_in"); - String accessToken = accessTokenData.getString("access_token"); - String uuid = UUID.randomUUID().toString(); - - ///////////////////////////////// - // store the details in the db // - ///////////////////////////////// - QRecord accessTokenRecord = new QRecord() - .withValue(auth0MetaData.getClientAuth0ApplicationIdField(), clientAuth0Application.getValue("id")) - .withValue(auth0MetaData.getAuth0AccessTokenField(), accessToken) - .withValue(auth0MetaData.getQqqAccessTokenField(), uuid) - .withValue(auth0MetaData.getExpiresInSecondsField(), expiresInSeconds); - InsertInput input = new InsertInput(); - input.setTableName(auth0MetaData.getAccessTokenTableName()); - input.setRecords(List.of(accessTokenRecord)); - new InsertAction().execute(input); - - ////////////////////////////////// - // update and send the response // - ////////////////////////////////// - accessTokenData.put("access_token", uuid); - accessTokenData.remove("scope"); - return (accessTokenData.toString()); - } - catch(AccessTokenException ate) - { - throw (ate); - } - catch(Exception e) - { - throw (new AccessTokenException(e.getMessage(), e)); - } - finally - { - QContext.setQSession(sessionBefore); - } - } - - - - /******************************************************************************* - ** make http request to Auth0 for a new access token - ** - *******************************************************************************/ - public JSONObject requestAccessTokenFromAuth0(Auth0AuthenticationMetaData auth0MetaData, String clientId, String clientSecret) throws AccessTokenException + public JSONObject requestAccessTokenForClientIdAndSecretFromAuth0(Auth0AuthenticationMetaData auth0MetaData, String clientId, String clientSecret) throws AccessTokenException { /////////////////////////////////////////////////////////////////// // make a request to Auth0 using the client_id and client_secret // @@ -776,6 +792,63 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface + /******************************************************************************* + ** Look up access_token from session UUID + ** + *******************************************************************************/ + private String getAccessTokenFromSessionUUID(Auth0AuthenticationMetaData metaData, String sessionUUID) throws QAuthenticationException + { + String accessToken = null; + QSession beforeSession = QContext.getQSession(); + + try + { + QContext.setQSession(getChickenAndEggSession()); + + /////////////////////////////////////// + // query for the user session record // + /////////////////////////////////////// + QRecord userSessionRecord = new GetAction().executeForRecord(new GetInput(UserSession.TABLE_NAME) + .withUniqueKey(Map.of("uuid", sessionUUID)) + .withShouldMaskPasswords(false) + .withShouldOmitHiddenFields(false)); + + if(userSessionRecord != null) + { + accessToken = userSessionRecord.getValueString("accessToken"); + + //////////////////////////////////////////////////////////// + // decode the accessToken and make sure it is not expired // + //////////////////////////////////////////////////////////// + if(accessToken != null) + { + DecodedJWT jwt = JWT.decode(accessToken); + if(jwt.getExpiresAtAsInstant().isBefore(Instant.now())) + { + throw (new QAuthenticationException("accessToken is expired")); + } + } + } + } + catch(QAuthenticationException qae) + { + throw (qae); + } + catch(Exception e) + { + LOG.warn("Error looking up userSession by sessionUUID", e); + throw (new QAuthenticationException("Error looking up userSession by sessionUUID", e)); + } + finally + { + QContext.setQSession(beforeSession); + } + + return (accessToken); + } + + + /******************************************************************************* ** Look up access_token from api key ** @@ -841,7 +914,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface // store the actual access token in the database, and return a unique value // // back to the user which will be what they use on subsequent requests (because token too big) // ///////////////////////////////////////////////////////////////////////////////////////////////// - JSONObject accessTokenData = requestAccessTokenFromAuth0(metaData, clientId, clientSecret); + JSONObject accessTokenData = requestAccessTokenForClientIdAndSecretFromAuth0(metaData, clientId, clientSecret); Integer expiresInSeconds = accessTokenData.getInt("expires_in"); accessToken = accessTokenData.getString("access_token"); @@ -869,28 +942,4 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface return (accessToken); } - - - /******************************************************************************* - ** Look up client_auth0_application record, return if found. - ** - *******************************************************************************/ - QRecord getClientAuth0Application(Auth0AuthenticationMetaData metaData, String clientId) throws QException - { - ////////////////////////////////////////////////////////////////////////////////////// - // try to look up existing auth0 application from database, insert one if not found // - ////////////////////////////////////////////////////////////////////////////////////// - QueryInput queryInput = new QueryInput(); - queryInput.setTableName(metaData.getClientAuth0ApplicationTableName()); - queryInput.setFilter(new QQueryFilter(new QFilterCriteria(metaData.getAuth0ClientIdField(), QCriteriaOperator.EQUALS, clientId))); - QueryOutput queryOutput = new QueryAction().execute(queryInput); - - if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords())) - { - return (queryOutput.getRecords().get(0)); - } - - throw (new AccessTokenException("This client has not been configured to use the API.", HttpStatus.SC_UNAUTHORIZED)); - } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/metadata/UserSessionMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/metadata/UserSessionMetaDataProducer.java new file mode 100644 index 00000000..c5dca6e2 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/metadata/UserSessionMetaDataProducer.java @@ -0,0 +1,73 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.authentication.implementations.metadata; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel; +import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.model.UserSession; + + +/******************************************************************************* + ** Meta Data Producer for UserSession + *******************************************************************************/ +public class UserSessionMetaDataProducer extends MetaDataProducer +{ + private final String backendName; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public UserSessionMetaDataProducer(String backendName) + { + this.backendName = backendName; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QTableMetaData produce(QInstance qInstance) throws QException + { + QTableMetaData tableMetaData = new QTableMetaData() + .withName(UserSession.TABLE_NAME) + .withBackendName(backendName) + .withRecordLabelFormat("%s") + .withRecordLabelFields("id") + .withPrimaryKeyField("id") + .withUniqueKey(new UniqueKey("uuid")) + .withFieldsFromEntity(UserSession.class) + .withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE)); + return tableMetaData; + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/model/UserSession.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/model/UserSession.java new file mode 100644 index 00000000..2e5b1a3b --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/model/UserSession.java @@ -0,0 +1,262 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.authentication.implementations.model; + + +import java.time.Instant; +import com.kingsrook.qqq.backend.core.model.data.QField; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; +import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; + + +/******************************************************************************* + ** QRecord Entity for UserSession table + *******************************************************************************/ +public class UserSession extends QRecordEntity +{ + public static final String TABLE_NAME = "userSession"; + + @QField(isEditable = false) + private Integer id; + + @QField(isEditable = false) + private Instant createDate; + + @QField(isEditable = false) + private Instant modifyDate; + + @QField(isEditable = false, isHidden = true, maxLength = 40, valueTooLongBehavior = ValueTooLongBehavior.ERROR) + private String uuid; + + @QField(isEditable = false, isHidden = true) + private String accessToken; + + @QField(isEditable = false, maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS) + private String userId; + + + + /******************************************************************************* + ** Default constructor + *******************************************************************************/ + public UserSession() + { + } + + + + /******************************************************************************* + ** Constructor that takes a QRecord + *******************************************************************************/ + public UserSession(QRecord record) + { + populateFromQRecord(record); + } + + + + /******************************************************************************* + ** Getter for id + *******************************************************************************/ + public Integer getId() + { + return (this.id); + } + + + + /******************************************************************************* + ** Setter for id + *******************************************************************************/ + public void setId(Integer id) + { + this.id = id; + } + + + + /******************************************************************************* + ** Fluent setter for id + *******************************************************************************/ + public UserSession withId(Integer id) + { + this.id = id; + return (this); + } + + + + /******************************************************************************* + ** Getter for createDate + *******************************************************************************/ + public Instant getCreateDate() + { + return (this.createDate); + } + + + + /******************************************************************************* + ** Setter for createDate + *******************************************************************************/ + public void setCreateDate(Instant createDate) + { + this.createDate = createDate; + } + + + + /******************************************************************************* + ** Fluent setter for createDate + *******************************************************************************/ + public UserSession withCreateDate(Instant createDate) + { + this.createDate = createDate; + return (this); + } + + + + /******************************************************************************* + ** Getter for modifyDate + *******************************************************************************/ + public Instant getModifyDate() + { + return (this.modifyDate); + } + + + + /******************************************************************************* + ** Setter for modifyDate + *******************************************************************************/ + public void setModifyDate(Instant modifyDate) + { + this.modifyDate = modifyDate; + } + + + + /******************************************************************************* + ** Fluent setter for modifyDate + *******************************************************************************/ + public UserSession withModifyDate(Instant modifyDate) + { + this.modifyDate = modifyDate; + return (this); + } + + + + /******************************************************************************* + ** Getter for uuid + *******************************************************************************/ + public String getUuid() + { + return (this.uuid); + } + + + + /******************************************************************************* + ** Setter for uuid + *******************************************************************************/ + public void setUuid(String uuid) + { + this.uuid = uuid; + } + + + + /******************************************************************************* + ** Fluent setter for uuid + *******************************************************************************/ + public UserSession withUuid(String uuid) + { + this.uuid = uuid; + return (this); + } + + + + /******************************************************************************* + ** Getter for accessToken + *******************************************************************************/ + public String getAccessToken() + { + return (this.accessToken); + } + + + + /******************************************************************************* + ** Setter for accessToken + *******************************************************************************/ + public void setAccessToken(String accessToken) + { + this.accessToken = accessToken; + } + + + + /******************************************************************************* + ** Fluent setter for accessToken + *******************************************************************************/ + public UserSession withAccessToken(String accessToken) + { + this.accessToken = accessToken; + return (this); + } + + + + /******************************************************************************* + ** Getter for userId + *******************************************************************************/ + public String getUserId() + { + return (this.userId); + } + + + + /******************************************************************************* + ** Setter for userId + *******************************************************************************/ + public void setUserId(String userId) + { + this.userId = userId; + } + + + + /******************************************************************************* + ** Fluent setter for userId + *******************************************************************************/ + public UserSession withUserId(String userId) + { + this.userId = userId; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java index ecb5aca5..95b8d091 100755 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/CollectionUtils.java @@ -663,4 +663,19 @@ public class CollectionUtils return (output); } + + + /******************************************************************************* + ** + *******************************************************************************/ + public static boolean containsKeyWithNonNullValue(Map map, K key) + { + if(map == null) + { + return (false); + } + + return (map.containsKey(key) && map.get(key) != null); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java index 9a5d906e..2c791804 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/Auth0AuthenticationModuleTest.java @@ -42,7 +42,7 @@ import com.kingsrook.qqq.backend.core.state.SimpleStateKey; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import org.json.JSONObject; import org.junit.jupiter.api.Test; -import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.AUTH0_ACCESS_TOKEN_KEY; +import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.ACCESS_TOKEN_KEY; import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.BASIC_AUTH_KEY; import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.COULD_NOT_DECODE_ERROR; import static com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule.EXPIRED_TOKEN_ERROR; @@ -143,7 +143,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest public void testInvalidToken() { Map context = new HashMap<>(); - context.put(AUTH0_ACCESS_TOKEN_KEY, INVALID_TOKEN); + context.put(ACCESS_TOKEN_KEY, INVALID_TOKEN); try { @@ -167,7 +167,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest public void testUndecodableToken() { Map context = new HashMap<>(); - context.put(AUTH0_ACCESS_TOKEN_KEY, UNDECODABLE_TOKEN); + context.put(ACCESS_TOKEN_KEY, UNDECODABLE_TOKEN); try { @@ -191,7 +191,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest public void testProperlyFormattedButExpiredToken() { Map context = new HashMap<>(); - context.put(AUTH0_ACCESS_TOKEN_KEY, EXPIRED_TOKEN); + context.put(ACCESS_TOKEN_KEY, EXPIRED_TOKEN); try { @@ -236,7 +236,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest public void testNullToken() { Map context = new HashMap<>(); - context.put(AUTH0_ACCESS_TOKEN_KEY, null); + context.put(ACCESS_TOKEN_KEY, null); try { @@ -267,7 +267,7 @@ public class Auth0AuthenticationModuleTest extends BaseTest auth0Spy.createSession(qInstance, context); auth0Spy.createSession(qInstance, context); auth0Spy.createSession(qInstance, context); - verify(auth0Spy, times(1)).getAccessTokenFromAuth0(any(), any(), any()); + verify(auth0Spy, times(1)).getAccessTokenForUsernameAndPasswordFromAuth0(any(), any(), any()); } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java index 380979d0..cd99af77 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java @@ -27,7 +27,6 @@ import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -58,7 +57,6 @@ import com.kingsrook.qqq.api.model.openapi.HttpMethod; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.context.QContext; -import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException; import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; @@ -75,15 +73,12 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; -import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QUser; -import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; -import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; @@ -137,11 +132,6 @@ public class QJavalinApiHandler { return (() -> { - ///////////////////////////// - // authentication endpoint // - ///////////////////////////// - ApiBuilder.post("/api/oauth/token", QJavalinApiHandler::handleAuthorization); - /////////////////////////////////////////////// // static endpoints to support rapidoc pages // /////////////////////////////////////////////// @@ -583,101 +573,6 @@ public class QJavalinApiHandler - /******************************************************************************* - ** - *******************************************************************************/ - private static void handleAuthorization(Context context) - { - try - { - //////////////////////////////////////////////////////////////////////////////////////////////////////// - // clientId & clientSecret may either be provided as formParams, or in an Authorization: Basic header // - //////////////////////////////////////////////////////////////////////////////////////////////////////// - String clientId; - String clientSecret; - String authorizationHeader = context.header("Authorization"); - if(authorizationHeader != null && authorizationHeader.startsWith("Basic ")) - { - try - { - byte[] credDecoded = Base64.getDecoder().decode(authorizationHeader.replace("Basic ", "")); - String credentials = new String(credDecoded, StandardCharsets.UTF_8); - String[] parts = credentials.split(":", 2); - clientId = parts[0]; - clientSecret = parts[1]; - } - catch(Exception e) - { - context.status(HttpStatus.BAD_REQUEST_400); - context.result("Could not parse client_id and client_secret from Basic Authorization header."); - return; - } - } - else - { - clientId = context.formParam("client_id"); - if(clientId == null) - { - context.status(HttpStatus.BAD_REQUEST_400); - context.result("'client_id' must be provided."); - return; - } - clientSecret = context.formParam("client_secret"); - if(clientSecret == null) - { - context.status(HttpStatus.BAD_REQUEST_400); - context.result("'client_secret' must be provided."); - return; - } - } - - //////////////////////////////////////////////////////// - // get the auth0 authentication module from qInstance // - //////////////////////////////////////////////////////// - Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication(); - QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher(); - QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); - - try - { - ////////////////////////////////////////////////////////////////////////////////////////// - // make call to get access token data, if no exception thrown, assume 200 OK and return // - ////////////////////////////////////////////////////////////////////////////////////////// - QContext.init(qInstance, null); // hmm... - String accessToken = authenticationModule.createAccessToken(metaData, clientId, clientSecret); - context.status(HttpStatus.Code.OK.getCode()); - context.result(accessToken); - QJavalinAccessLogger.logEndSuccess(); - } - catch(AccessTokenException aae) - { - LOG.info("Error getting api access token", aae, logPair("clientId", clientId)); - - /////////////////////////////////////////////////////////////////////////// - // if the exception has a status code, then return that code and message // - /////////////////////////////////////////////////////////////////////////// - if(aae.getStatusCode() != null) - { - context.status(aae.getStatusCode()); - context.result(aae.getMessage()); - QJavalinAccessLogger.logEndSuccess(); - } - - //////////////////////////////////////////////////////// - // if no code, throw and handle like other exceptions // - //////////////////////////////////////////////////////// - throw (aae); - } - } - catch(Exception e) - { - handleException(context, e); - QJavalinAccessLogger.logEndFail(e); - } - } - - - /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 172d9544..4799b12a 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -113,6 +113,7 @@ import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.statusmessages.QStatusMessage; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; +import com.kingsrook.qqq.backend.core.modules.authentication.implementations.Auth0AuthenticationModule; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; @@ -148,10 +149,10 @@ public class QJavalinImplementation { private static final QLogger LOG = QLogger.getLogger(QJavalinImplementation.class); - public static final int SESSION_COOKIE_AGE = 60 * 60 * 24; - public static final String SESSION_ID_COOKIE_NAME = "sessionId"; - public static final String BASIC_AUTH_NAME = "basicAuthString"; - public static final String API_KEY_NAME = "apiKey"; + public static final int SESSION_COOKIE_AGE = 60 * 60 * 24; + public static final String SESSION_ID_COOKIE_NAME = "sessionId"; + public static final String SESSION_UUID_COOKIE_NAME = "sessionUUID"; + public static final String API_KEY_NAME = "apiKey"; static QInstance qInstance; static QJavalinMetaData javalinMetaData; @@ -329,6 +330,8 @@ public class QJavalinImplementation { return (() -> { + post("/manageSession", QJavalinImplementation::manageSession); + ///////////////////// // metadata routes // ///////////////////// @@ -400,6 +403,36 @@ public class QJavalinImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private static void manageSession(Context context) + { + try + { + Map map = context.bodyAsClass(Map.class); + + QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher(); + QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); + + Map authContext = new HashMap<>(); + //? authContext.put("uuid", ValueUtils.getValueAsString(map.get("uuid"))); + authContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, ValueUtils.getValueAsString(map.get("accessToken"))); + authContext.put(Auth0AuthenticationModule.DO_STORE_USER_SESSION_KEY, "true"); + + QSession session = authenticationModule.createSession(qInstance, authContext); + + context.cookie(SESSION_UUID_COOKIE_NAME, session.getUuid(), SESSION_COOKIE_AGE); + context.result(JsonUtils.toJson(MapBuilder.of("uuid", session.getUuid()))); + } + catch(Exception e) + { + handleException(context, e); + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -442,10 +475,12 @@ public class QJavalinImplementation { Map authenticationContext = new HashMap<>(); - String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME); + // todo delete String sessionIdCookieValue = context.cookie(SESSION_ID_COOKIE_NAME); + String sessionUuidCookieValue = context.cookie(Auth0AuthenticationModule.SESSION_UUID_KEY); String authorizationHeaderValue = context.header("Authorization"); String apiKeyHeaderValue = context.header("x-api-key"); + /* todo - change to sessionUUID. if(StringUtils.hasContent(sessionIdCookieValue)) { //////////////////////////////////////// @@ -453,6 +488,11 @@ public class QJavalinImplementation //////////////////////////////////////// authenticationContext.put(SESSION_ID_COOKIE_NAME, sessionIdCookieValue); } + else*/ + if(StringUtils.hasContent(sessionUuidCookieValue)) + { + authenticationContext.put(Auth0AuthenticationModule.SESSION_UUID_KEY, sessionUuidCookieValue); + } else if(apiKeyHeaderValue != null) { ///////////////////////////////////////////////////////////////// @@ -533,12 +573,12 @@ public class QJavalinImplementation if(authorizationHeaderValue.startsWith(basicPrefix)) { authorizationHeaderValue = authorizationHeaderValue.replaceFirst(basicPrefix, ""); - authenticationContext.put(BASIC_AUTH_NAME, authorizationHeaderValue); + authenticationContext.put(Auth0AuthenticationModule.BASIC_AUTH_KEY, authorizationHeaderValue); } else if(authorizationHeaderValue.startsWith(bearerPrefix)) { authorizationHeaderValue = authorizationHeaderValue.replaceFirst(bearerPrefix, ""); - authenticationContext.put(SESSION_ID_COOKIE_NAME, authorizationHeaderValue); + authenticationContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, authorizationHeaderValue); } else { diff --git a/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index bc47eeb9..620433ab 100644 --- a/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -90,8 +90,6 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; import io.github.cdimascio.dotenv.Dotenv; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.core.config.Configurator; -import org.jline.reader.LineReader; -import org.jline.reader.LineReaderBuilder; import org.jline.utils.Log; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; @@ -292,18 +290,7 @@ public class QPicoCliImplementation } Map authenticationContext = new HashMap<>(); - if(sessionId == null && authenticationModule instanceof Auth0AuthenticationModule) - { - LineReader lr = LineReaderBuilder.builder().build(); - String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete..."); - dotenv = loadDotEnv(); - if(dotenv.isPresent()) - { - sessionId = dotenv.get().get("SESSION_ID"); - } - } - - authenticationContext.put("sessionId", sessionId); + authenticationContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, sessionId); // todo - does this need some per-provider logic actually? mmm... session = authenticationModule.createSession(qInstance, authenticationContext);