diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QAuthenticationType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QAuthenticationType.java
index 9ae00fbd..e8ecedf8 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QAuthenticationType.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QAuthenticationType.java
@@ -28,6 +28,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
*******************************************************************************/
public enum QAuthenticationType
{
+ OAUTH2("OAuth2"),
AUTH_0("auth0"),
TABLE_BASED("tableBased"),
FULLY_ANONYMOUS("fullyAnonymous"),
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/OAuth2AuthenticationMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/OAuth2AuthenticationMetaData.java
new file mode 100644
index 00000000..0c5f585c
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/authentication/OAuth2AuthenticationMetaData.java
@@ -0,0 +1,192 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. 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.model.metadata.authentication;
+
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
+import com.kingsrook.qqq.backend.core.modules.authentication.implementations.OAuth2AuthenticationModule;
+
+
+/*******************************************************************************
+ ** Meta-data to provide details of an OAuth2 Authentication module
+ *******************************************************************************/
+public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
+{
+ private String baseUrl;
+ private String tokenUrl;
+ private String clientId;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // keep this secret, on the server - don't let it be serialized and sent to a client! //
+ ////////////////////////////////////////////////////////////////////////////////////////
+ @JsonIgnore
+ private String clientSecret;
+
+
+
+ /*******************************************************************************
+ ** Default Constructor.
+ *******************************************************************************/
+ public OAuth2AuthenticationMetaData()
+ {
+ super();
+ setType(QAuthenticationType.OAUTH2);
+
+ //////////////////////////////////////////////////////////
+ // ensure this module is registered with the dispatcher //
+ //////////////////////////////////////////////////////////
+ QAuthenticationModuleDispatcher.registerModule(QAuthenticationType.OAUTH2.getName(), OAuth2AuthenticationModule.class.getName());
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter, override to help fluent flows
+ *******************************************************************************/
+ public OAuth2AuthenticationMetaData withBaseUrl(String baseUrl)
+ {
+ setBaseUrl(baseUrl);
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for baseUrl
+ **
+ *******************************************************************************/
+ public String getBaseUrl()
+ {
+ return baseUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for baseUrl
+ **
+ *******************************************************************************/
+ public void setBaseUrl(String baseUrl)
+ {
+ this.baseUrl = baseUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter, override to help fluent flows
+ *******************************************************************************/
+ public OAuth2AuthenticationMetaData withClientId(String clientId)
+ {
+ setClientId(clientId);
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for clientId
+ **
+ *******************************************************************************/
+ public String getClientId()
+ {
+ return clientId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for clientId
+ **
+ *******************************************************************************/
+ public void setClientId(String clientId)
+ {
+ this.clientId = clientId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter, override to help fluent flows
+ *******************************************************************************/
+ public OAuth2AuthenticationMetaData withClientSecret(String clientSecret)
+ {
+ setClientSecret(clientSecret);
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for clientSecret
+ **
+ *******************************************************************************/
+ public String getClientSecret()
+ {
+ return clientSecret;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for clientSecret
+ **
+ *******************************************************************************/
+ public void setClientSecret(String clientSecret)
+ {
+ this.clientSecret = clientSecret;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tokenUrl
+ *******************************************************************************/
+ public String getTokenUrl()
+ {
+ return (this.tokenUrl);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for tokenUrl
+ *******************************************************************************/
+ public void setTokenUrl(String tokenUrl)
+ {
+ this.tokenUrl = tokenUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for tokenUrl
+ *******************************************************************************/
+ public OAuth2AuthenticationMetaData withTokenUrl(String tokenUrl)
+ {
+ this.tokenUrl = tokenUrl;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QUser.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QUser.java
index 1adcc3a4..787da174 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QUser.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/session/QUser.java
@@ -22,10 +22,13 @@
package com.kingsrook.qqq.backend.core.model.session;
+import java.io.Serializable;
+
+
/*******************************************************************************
**
*******************************************************************************/
-public class QUser implements Cloneable
+public class QUser implements Cloneable, Serializable
{
private String idReference;
private String fullName;
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleInterface.java
index 28db59d6..b296d348 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleInterface.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QAuthenticationModuleInterface.java
@@ -25,13 +25,10 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
import java.io.Serializable;
import java.util.Map;
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.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
-import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
@@ -81,13 +78,4 @@ public interface QAuthenticationModuleInterface
return (false);
}
-
- /*******************************************************************************
- **
- *******************************************************************************/
- default String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
- {
- throw (new NotImplementedException("The method createAccessToken() is not implemented in the class: " + this.getClass().getSimpleName()));
- }
-
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/FullyAnonymousAuthenticationModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/FullyAnonymousAuthenticationModule.java
index 457f6bbb..519b609f 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/FullyAnonymousAuthenticationModule.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/FullyAnonymousAuthenticationModule.java
@@ -24,9 +24,7 @@ package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
import java.util.Map;
import java.util.UUID;
-import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
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;
@@ -77,15 +75,4 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
return session != null;
}
-
-
- /*******************************************************************************
- ** Load an instance of the appropriate state provider
- **
- *******************************************************************************/
- public String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
- {
- return (TEST_ACCESS_TOKEN);
- }
-
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/OAuth2AuthenticationModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/OAuth2AuthenticationModule.java
new file mode 100644
index 00000000..893a47ef
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/OAuth2AuthenticationModule.java
@@ -0,0 +1,340 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. 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;
+
+
+import java.io.Serializable;
+import java.net.URI;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
+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.CapturedContext;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+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.OAuth2AuthenticationMetaData;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
+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.utils.memoization.Memoization;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import org.json.JSONObject;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ ** Implementation of OAuth2 authentication.
+ *******************************************************************************/
+public class OAuth2AuthenticationModule implements QAuthenticationModuleInterface
+{
+ private static final QLogger LOG = QLogger.getLogger(OAuth2AuthenticationModule.class);
+
+ private static boolean mayMemoize = true;
+
+ private static final Memoization getAccessTokenFromSessionUUIDMemoization = new Memoization()
+ .withTimeout(Duration.of(1, ChronoUnit.MINUTES))
+ .withMaxSize(1000);
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public QSession createSession(QInstance qInstance, Map context) throws QAuthenticationException
+ {
+ try
+ {
+ OAuth2AuthenticationMetaData oauth2MetaData = (OAuth2AuthenticationMetaData) qInstance.getAuthentication();
+
+ if(context.containsKey("code") && context.containsKey("redirectUri") && context.containsKey("codeVerifier"))
+ {
+ AuthorizationCode code = new AuthorizationCode(context.get("code"));
+ URI callback = new URI(context.get("redirectUri"));
+ CodeVerifier codeVerifier = new CodeVerifier(context.get("codeVerifier"));
+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, callback, codeVerifier);
+
+ ClientID clientID = new ClientID(oauth2MetaData.getClientId());
+ Secret clientSecret = new Secret(oauth2MetaData.getClientSecret());
+ ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);
+
+ URI tokenEndpoint = new URI(oauth2MetaData.getTokenUrl());
+ Scope scope = new Scope("openid profile email offline_access");
+ TokenRequest tokenRequest = new TokenRequest(tokenEndpoint, clientAuth, codeGrant, scope);
+
+ TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send());
+
+ if(tokenResponse.indicatesSuccess())
+ {
+ AccessToken accessToken = tokenResponse.toSuccessResponse().getTokens().getAccessToken();
+ // todo - what?? RefreshToken refreshToken = tokenResponse.toSuccessResponse().getTokens().getRefreshToken();
+
+ QSession session = createSessionFromToken(accessToken.getValue());
+ insertUserSession(accessToken.getValue(), session);
+ return (session);
+ }
+ else
+ {
+ ErrorObject errorObject = tokenResponse.toErrorResponse().getErrorObject();
+ LOG.info("Token request failed", logPair("code", errorObject.getCode()), logPair("description", errorObject.getDescription()));
+ throw (new QAuthenticationException(errorObject.getDescription()));
+ }
+ }
+ else if(context.containsKey("sessionUUID") || context.containsKey("uuid"))
+ {
+ String uuid = Objects.requireNonNullElseGet(context.get("sessionUUID"), () -> context.get("uuid"));
+ String accessToken = getAccessTokenFromSessionUUID(uuid);
+ QSession session = createSessionFromToken(accessToken);
+ session.setUuid(uuid);
+ // todo - validate its age or against provider??
+ return (session);
+ }
+ else
+ {
+ String message = "Did not receive recognized values in context for creating session";
+ LOG.warn(message, logPair("contextKeys", context.keySet()));
+ throw (new QAuthenticationException(message));
+ }
+ }
+ catch(QAuthenticationException qae)
+ {
+ throw (qae);
+ }
+ catch(Exception e)
+ {
+ throw (new QAuthenticationException("Failed to create session (token)", e));
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean isSessionValid(QInstance instance, QSession session)
+ {
+ if(session instanceof QSystemUserSession)
+ {
+ return (true);
+ }
+
+ try
+ {
+ String accessToken = getAccessTokenFromSessionUUID(session.getUuid());
+ DecodedJWT jwt = JWT.decode(accessToken);
+ if(jwt.getExpiresAtAsInstant().isBefore(Instant.now()))
+ {
+ LOG.debug("accessToken is expired", logPair("sessionUUID", session.getUuid()));
+ return (false);
+ }
+
+ return true;
+ }
+ catch(QAuthenticationException e)
+ {
+ return (false);
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private QSession createSessionFromToken(String accessToken) throws QException
+ {
+ DecodedJWT jwt = JWT.decode(accessToken);
+ Base64.Decoder decoder = Base64.getUrlDecoder();
+ String payloadString = new String(decoder.decode(jwt.getPayload()));
+ JSONObject payload = new JSONObject(payloadString);
+
+ QSession session = new QSession();
+ QUser user = new QUser();
+ session.setUser(user);
+
+ user.setFullName("Unknown");
+ String email = Objects.requireNonNullElseGet(payload.optString("email", null), () -> payload.optString("sub", null));
+ String name = payload.optString("name", email);
+
+ user.setIdReference(email);
+ user.setFullName(name);
+
+ ////////////////////////////////////////////////////////////
+ // todo - this needs to be much better standardized w/ fe //
+ ////////////////////////////////////////////////////////////
+ session.withValueForFrontend("user", new HashMap<>(Map.of("name", name, "email", email)));
+
+ return session;
+ }
+
+
+
+ /*******************************************************************************
+ ** Insert a session as a new record into userSession table
+ *******************************************************************************/
+ private void insertUserSession(String accessToken, QSession qSession) throws QException
+ {
+ CapturedContext capturedContext = QContext.capture();
+ try
+ {
+ QContext.init(capturedContext.qInstance(), new QSystemUserSession());
+
+ 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);
+ }
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public QSession createAutomatedSessionForUser(QInstance qInstance, Serializable userId) throws QAuthenticationException
+ {
+ return QAuthenticationModuleInterface.super.createAutomatedSessionForUser(qInstance, userId);
+ }
+
+
+
+ /*******************************************************************************
+ ** Look up access_token from session UUID
+ **
+ *******************************************************************************/
+ private String getAccessTokenFromSessionUUID(String sessionUUID) throws QAuthenticationException
+ {
+ if(mayMemoize)
+ {
+ return getAccessTokenFromSessionUUIDMemoization.getResultThrowing(sessionUUID, (String x) ->
+ doGetAccessTokenFromSessionUUID(sessionUUID)
+ ).orElse(null);
+ }
+ else
+ {
+ return (doGetAccessTokenFromSessionUUID(sessionUUID));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private String doGetAccessTokenFromSessionUUID(String sessionUUID) throws QAuthenticationException
+ {
+ String accessToken = null;
+ QSession beforeSession = QContext.getQSession();
+
+ try
+ {
+ QContext.setQSession(new QSystemUserSession());
+
+ ///////////////////////////////////////
+ // 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);
+ }
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public boolean usesSessionIdCookie()
+ {
+ return (false);
+ }
+
+}
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 80f227d3..349e4d49 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
@@ -453,10 +453,21 @@ public class QJavalinImplementation
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");
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // before this code iterated the map, it had zombied uuid line, and only actually used ACCESS_TOKEN_KEY //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //? authContext.put("uuid", ValueUtils.getValueAsString(map.get("uuid")));
+ // authContext.put(Auth0AuthenticationModule.ACCESS_TOKEN_KEY, ValueUtils.getValueAsString(map.get("accessToken")));
+ /////////////////////////////////////////////////////////////////
+ // todo - have the auth module declare what values it expects? //
+ /////////////////////////////////////////////////////////////////
+ for(Map.Entry, ?> entry : map.entrySet())
+ {
+ authContext.put(ValueUtils.getValueAsString(entry.getKey()), ValueUtils.getValueAsString(entry.getValue()));
+ }
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// put the qInstance into context - but no session yet (since, the whole point of this call is to manage the session!) //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/OAuth2MetaDataProvider.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/OAuth2MetaDataProvider.java
new file mode 100644
index 00000000..ce6310dd
--- /dev/null
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/OAuth2MetaDataProvider.java
@@ -0,0 +1,63 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. 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.sampleapp.metadata;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
+import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.OAuth2AuthenticationMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
+
+
+/*******************************************************************************
+ ** Provides all OAuth2 authentication related metadata to the QQQ engine
+ *
+ *******************************************************************************/
+public class OAuth2MetaDataProvider implements MetaDataProducerInterface
+{
+ public static final String NAME = "OAuth2";
+
+
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ @Override
+ public QAuthenticationMetaData produce(QInstance qInstance) throws QException
+ {
+ QMetaDataVariableInterpreter qMetaDataVariableInterpreter = new QMetaDataVariableInterpreter();
+
+ String oauth2BaseUrl = qMetaDataVariableInterpreter.interpret("${env.OAUTH2_BASE_URL}");
+ String oauth2TokenUrl = qMetaDataVariableInterpreter.interpret("${env.OAUTH2_TOKEN_URL}");
+ String oauth2ClientId = qMetaDataVariableInterpreter.interpret("${env.OAUTH2_CLIENT_ID}");
+ String oauth2ClientSecret = qMetaDataVariableInterpreter.interpret("${env.OAUTH2_CLIENT_SECRET}");
+
+ return (new OAuth2AuthenticationMetaData()
+ .withBaseUrl(oauth2BaseUrl)
+ .withTokenUrl(oauth2TokenUrl)
+ .withClientId(oauth2ClientId)
+ .withClientSecret(oauth2ClientSecret)
+ .withName(NAME));
+ }
+}
diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/SampleMetaDataProvider.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/SampleMetaDataProvider.java
index bc40d1a1..c3194006 100644
--- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/SampleMetaDataProvider.java
+++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/metadata/SampleMetaDataProvider.java
@@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInpu
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
@@ -71,6 +72,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
+import com.kingsrook.qqq.backend.core.modules.authentication.implementations.metadata.UserSessionMetaDataProducer;
+import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
@@ -97,6 +100,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
public static final String RDBMS_BACKEND_NAME = "rdbms";
public static final String FILESYSTEM_BACKEND_NAME = "filesystem";
+ public static final String MEMORY_BACKEND_NAME = "memory";
public static final String APP_NAME_GREETINGS = "greetingsApp";
public static final String APP_NAME_PEOPLE = "peopleApp";
@@ -140,8 +144,9 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
{
QInstance qInstance = new QInstance();
- qInstance.setAuthentication(defineAuthentication());
+ // qInstance.setAuthentication(defineAuthentication());
qInstance.addBackend(defineRdbmsBackend());
+ qInstance.addBackend(defineMemoryBackend());
qInstance.addBackend(defineFilesystemBackend());
qInstance.addTable(defineTableCarrier());
qInstance.addTable(defineTablePerson());
@@ -157,6 +162,8 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
qInstance.addProcess(defineProcessScreenThenSleep());
qInstance.addProcess(defineProcessSimpleThrow());
+ qInstance.addTable(new UserSessionMetaDataProducer(MEMORY_BACKEND_NAME).produce(qInstance));
+
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, SampleMetaDataProvider.class.getPackageName());
defineWidgets(qInstance);
@@ -168,6 +175,18 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ private static QBackendMetaData defineMemoryBackend()
+ {
+ return new QBackendMetaData()
+ .withName(MEMORY_BACKEND_NAME)
+ .withBackendType(MemoryBackendModule.class);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/