Skip to content

Commit

Permalink
fix: introspect api
Browse files Browse the repository at this point in the history
  • Loading branch information
sattvikc committed Sep 10, 2024
1 parent 7f413c6 commit c620cdf
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 1 deletion.
24 changes: 24 additions & 0 deletions src/main/java/io/supertokens/oauth/OAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import io.supertokens.Main;
import io.supertokens.config.Config;
import io.supertokens.exceptions.TryRefreshTokenException;
import io.supertokens.featureflag.EE_FEATURES;
import io.supertokens.featureflag.FeatureFlag;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
Expand All @@ -39,6 +40,7 @@
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.oauth.OAuthStorage;
import io.supertokens.pluginInterface.oauth.exceptions.OAuth2ClientAlreadyExistsForAppException;
import io.supertokens.session.accessToken.AccessToken;
import io.supertokens.session.jwt.JWT.JWTException;
import io.supertokens.signingkeys.JWTSigningKey;
import io.supertokens.signingkeys.SigningKeys;
Expand Down Expand Up @@ -381,4 +383,26 @@ public int getValue() {
return value;
}
}

public static JsonObject introspectAccessToken(Main main, AppIdentifier appIdentifier, Storage storage,
String token) throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException, UnsupportedJWTSigningAlgorithmException {
try {
JsonObject payload = AccessToken.getPayloadFromAccessToken(appIdentifier, main, token);
if (payload.has("stt") && payload.get("stt").getAsInt() == SessionTokenType.ACCESS_TOKEN.value) {
payload.addProperty("active", true);
payload.addProperty("token_type", "Bearer");
payload.addProperty("token_use", "access_token");

return payload;
}
// else fallback to active: false

} catch (TryRefreshTokenException e) {
// fallback to active: false
}

JsonObject result = new JsonObject();
result.addProperty("active", false);
return result;
}
}
40 changes: 40 additions & 0 deletions src/main/java/io/supertokens/session/accessToken/AccessToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,46 @@

public class AccessToken {

public static JsonObject getPayloadFromAccessToken(AppIdentifier appIdentifier,
@Nonnull Main main, @Nonnull String token)
throws TenantOrAppNotFoundException, TryRefreshTokenException, StorageQueryException,
UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException {
List<JWTSigningKeyInfo> keyInfoList = SigningKeys.getInstance(appIdentifier, main).getAllKeys();
Exception error = null;
JWT.JWTInfo jwtInfo = null;
JWT.JWTPreParseInfo preParseJWTInfo = null;
try {
preParseJWTInfo = JWT.preParseJWTInfo(token);
} catch (JWTException e) {
// This basically should never happen, but it means, that the token structure is wrong, can't verify
throw new TryRefreshTokenException(e);
}

for (JWTSigningKeyInfo keyInfo : keyInfoList) {
try {
jwtInfo = JWT.verifyJWTAndGetPayload(preParseJWTInfo,
((JWTAsymmetricSigningKeyInfo) keyInfo).publicKey);
error = null;
break;
} catch (NoSuchAlgorithmException e) {
// This basically should never happen, but it means, that can't verify any tokens, no need to retry
throw new TryRefreshTokenException(e);
} catch (KeyException | JWTException e) {
error = e;
}
}

if (jwtInfo == null) {
throw new TryRefreshTokenException(error);
}

if (jwtInfo.payload.get("exp").getAsLong() * 1000 < System.currentTimeMillis()) {
throw new TryRefreshTokenException("Access token expired");
}

return jwtInfo.payload;
}

// TODO: device fingerprint - store hash of this in JWT.

private static AccessTokenInfo getInfoFromAccessToken(AppIdentifier appIdentifier,
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/supertokens/webserver/Webserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import io.supertokens.webserver.api.oauth.OAuthAcceptAuthLoginRequestAPI;
import io.supertokens.webserver.api.oauth.OAuthAcceptAuthLogoutRequestAPI;
import io.supertokens.webserver.api.oauth.OAuthTokenAPI;
import io.supertokens.webserver.api.oauth.OAuthTokenIntrospectAPI;
import io.supertokens.webserver.api.oauth.RemoveOAuthClientAPI;
import io.supertokens.webserver.api.passwordless.*;
import io.supertokens.webserver.api.session.*;
Expand Down Expand Up @@ -296,6 +297,7 @@ private void setupRoutes() {
addAPI(new OAuthGetAuthLogoutRequestAPI(main));
addAPI(new OAuthAcceptAuthLogoutRequestAPI(main));
addAPI(new OAuthRejectAuthLogoutRequestAPI(main));
addAPI(new OAuthTokenIntrospectAPI(main));

StandardContext context = tomcatReference.getContext();
Tomcat tomcat = tomcatReference.getTomcat();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OAuthTokenAPI extends WebserverAPI {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package io.supertokens.webserver.api.oauth;

import com.google.gson.*;
import io.supertokens.Main;
import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException;
import io.supertokens.multitenancy.exception.BadPermissionException;
import io.supertokens.oauth.OAuth;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.WebserverAPI;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class OAuthTokenIntrospectAPI extends WebserverAPI {

public OAuthTokenIntrospectAPI(Main main) {
super(main, RECIPE_ID.OAUTH.toString());
}

@Override
public String getPath() {
return "/recipe/oauth/introspect";
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
JsonObject input = InputParser.parseJsonObjectOrThrowError(req);
String token = InputParser.parseStringOrThrowError(input, "token", false);

if (token.startsWith("st_rt_")) {
String iss = InputParser.parseStringOrThrowError(input, "iss", false);

try {
OAuthProxyHelper.proxyFormPOST(
main, req, resp,
getAppIdentifier(req),
enforcePublicTenantAndGetPublicTenantStorage(req),
"/admin/oauth2/introspect",
true,
false,
() -> {
Map<String, String> formFields = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : input.entrySet()) {
formFields.put(entry.getKey(), entry.getValue().getAsString());
}

return formFields;
},
HashMap::new,
(statusCode, headers, rawBody, jsonBody) -> {
JsonObject jsonObject = jsonBody.getAsJsonObject();

jsonObject.addProperty("iss", iss);
if (jsonObject.has("ext")) {
JsonObject ext = jsonObject.get("ext").getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : ext.entrySet()) {
jsonObject.add(entry.getKey(), entry.getValue());
}
jsonObject.remove("ext");
}

jsonObject.addProperty("status", "OK");
super.sendJsonResponse(200, jsonBody, resp);
}
);
} catch (IOException | TenantOrAppNotFoundException | BadPermissionException e) {
throw new ServletException(e);
}
} else {
try {
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req);
JsonObject response = OAuth.introspectAccessToken(main, appIdentifier, storage, token);
super.sendJsonResponse(200, response, resp);

} catch (IOException | TenantOrAppNotFoundException | BadPermissionException | StorageQueryException | StorageTransactionLogicException | UnsupportedJWTSigningAlgorithmException e) {
throw new ServletException(e);
}
}
}
}

0 comments on commit c620cdf

Please sign in to comment.