Skip to content

Commit

Permalink
Cognito with both username and email
Browse files Browse the repository at this point in the history
  • Loading branch information
matusfaro committed Jan 3, 2024
1 parent a9a0040 commit 435a375
Show file tree
Hide file tree
Showing 26 changed files with 1,087 additions and 217 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -83,8 +83,7 @@ public Object handleRequest(APIGatewayCustomAuthorizerEvent event, Context conte
String authorizationValueLower = authorizationValue.toLowerCase();

final String identifier;
final String userEmail;
final String principalId;
final String username;
final ImmutableSet<String> organizationNames;
final ImmutableSet<String> queueWhitelist;
final Optional<String> usageKey;
Expand All @@ -96,12 +95,11 @@ public Object handleRequest(APIGatewayCustomAuthorizerEvent event, Context conte
.orElseThrow(() -> new ApiGatewayUnauthorized("Cognito JWT verification failed"));

// Extract access info
userEmail = verifiedCognitoJwt.getUserEmail();
principalId = verifiedCognitoJwt.getUserEmail();
username = verifiedCognitoJwt.getUsername();
organizationNames = verifiedCognitoJwt.getGroupNames();
queueWhitelist = ImmutableSet.of();
usageKey = apiAccessStore.getUsageKey(verifiedCognitoJwt.getUsageKeyType(), Optional.of(verifiedCognitoJwt.getUserEmail()), organizationNames);
identifier = "user " + verifiedCognitoJwt.getUserEmail() + " via cognito JWT";
usageKey = apiAccessStore.getUsageKey(verifiedCognitoJwt.getUsageKeyType(), Optional.of(verifiedCognitoJwt.getUsername()), organizationNames);
identifier = "user " + verifiedCognitoJwt.getUsername() + " via cognito JWT";

} else if (authorizationValueLower.startsWith("apikey ")) {

Expand All @@ -111,17 +109,16 @@ public Object handleRequest(APIGatewayCustomAuthorizerEvent event, Context conte
.orElseThrow(() -> new ApiGatewayUnauthorized("invalid apikey found"));

// Extract access info
userEmail = apiAccess.getOwnerEmail();
principalId = apiAccess.getPrincipalId();
username = apiAccess.getOwnerUsername();
organizationNames = ImmutableSet.of(apiAccess.getOrganizationName());
queueWhitelist = apiAccess.getQueueWhitelist();
usageKey = apiAccessStore.getUsageKey(apiAccess.getUsageKeyType(), Optional.of(apiAccess.getOwnerEmail()), ImmutableSet.of(apiAccess.getOrganizationName()));
usageKey = apiAccessStore.getUsageKey(apiAccess.getUsageKeyType(), Optional.of(apiAccess.getOwnerUsername()), ImmutableSet.of(apiAccess.getOrganizationName()));
switch (apiAccess.getOwnerType()) {
case USER -> identifier = "user " + apiAccess.getOwnerEmail() + " via apikey";
case USER -> identifier = "user " + apiAccess.getOwnerUsername() + " via apikey";
case TASK ->
identifier = "task " + apiAccess.getOwnerTaskId() + " version " + apiAccess.getOwnerTaskVersion() + " via apikey";
default ->
identifier = apiAccess.getOwnerType() + " owner email " + apiAccess.getOwnerEmail() + " via apikey";
identifier = apiAccess.getOwnerType() + " owner email " + apiAccess.getOwnerUsername() + " via apikey";
}

} else {
Expand All @@ -140,11 +137,11 @@ public Object handleRequest(APIGatewayCustomAuthorizerEvent event, Context conte
log.info("Client authorized for {}", identifier);
PolicyDocument policyDocument = generatePolicyDocument(region, awsAccountId, restApiId, stage, organizationNames, queueWhitelist);
return new AuthPolicy(
principalId,
username,
policyDocument,
usageKey,
Map.of(
AuthorizerConstants.CONTEXT_KEY_USER_EMAIL, userEmail,
AuthorizerConstants.CONTEXT_KEY_USERNAME, username,
AuthorizerConstants.CONTEXT_KEY_ORGANIZATION_NAMES, String.join(",", organizationNames)
));
} catch (ApiGatewayUnauthorized ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void test(TestType testType) throws Exception {
case GLOBAL -> "dataspray-usage-key-2-GLOBAL";
case UNLIMITED -> null;
}))
.body("context." + AuthorizerConstants.CONTEXT_KEY_USER_EMAIL, equalTo(apiAccess.getOwnerEmail()))
.body("context." + AuthorizerConstants.CONTEXT_KEY_USERNAME, equalTo(apiAccess.getOwnerUsername()))
.body("context." + AuthorizerConstants.CONTEXT_KEY_ORGANIZATION_NAMES, equalTo(apiAccess.getOrganizationName()))
.body("policyDocument", jsonStringEqualTo(ResourceUtil.getTestResource(
testType == TestType.AUTHORIZED_QUEUE_WHITELIST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@ApplicationScoped
public class AuthorizerConstants {

public static final String CONTEXT_KEY_USER_EMAIL = "userEmail";
public static final String CONTEXT_KEY_USERNAME = "username";
/** Value will hold comma delimited list of organization names */
public static final String CONTEXT_KEY_ORGANIZATION_NAMES = "organizationName";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -78,8 +78,8 @@ protected <T> AwsResponse<T> request(Class<T> bodyClazz, Given given) {
request.getRequestContext().setResourcePath(given.getPath());
request.getRequestContext().setHttpMethod(given.getMethod());
request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
request.getRequestContext().getAuthorizer().setPrincipalId(given.getUserEmail());
request.getRequestContext().getAuthorizer().setContextValue(AuthorizerConstants.CONTEXT_KEY_USER_EMAIL, String.join(",", given.getUserEmail()));
request.getRequestContext().getAuthorizer().setPrincipalId(given.getUsername());
request.getRequestContext().getAuthorizer().setContextValue(AuthorizerConstants.CONTEXT_KEY_USERNAME, String.join(",", given.getUsername()));
request.getRequestContext().getAuthorizer().setContextValue(AuthorizerConstants.CONTEXT_KEY_ORGANIZATION_NAMES, String.join(",", getOrganizationName()));
Response response = RestAssured.given()
.contentType("application/json")
Expand Down Expand Up @@ -116,7 +116,7 @@ protected static class Given {
MediaType contentType;
Object body;
@Builder.Default
String userEmail = "[email protected]";
String username = "username";
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -54,15 +54,15 @@ public abstract class AbstractResource {
protected UriInfo uriInfo;

/**
* Retrieve user email as passed in via context from Cognito Authorizer function.
* Retrieve username as passed in via context from Cognito Authorizer function.
* <p>
* May be empty if authorizer did not process this request, most likely as this API endpoint is open to public.
*/
protected Optional<String> getUserEmail() {
protected Optional<String> getUsername() {
return Optional.ofNullable(Strings.emptyToNull(proxyRequest
.getRequestContext()
.getAuthorizer()
.getContextValue(AuthorizerConstants.CONTEXT_KEY_USER_EMAIL)));
.getContextValue(AuthorizerConstants.CONTEXT_KEY_USERNAME)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -44,11 +44,11 @@ public enum TestType {
.path("/api/ping")
.build(),
"OK"),
USER_EMAIL(Given.builder()
.path("/api/user-email")
.userEmail("expected.[email protected]")
USERNAME(Given.builder()
.path("/api/username")
.username("expected.username")
.build(),
"expected.[email protected]");
"expected.username");

private final Given given;
private final String expectedBody;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -32,6 +32,6 @@ public interface ExampleApi {
String ping();

@GET
@Path("/user-email")
String userEmail();
@Path("/username")
String username();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Matus Faro
* Copyright 2024 Matus Faro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -33,7 +33,7 @@ public String ping() {
}

@Override
public String userEmail() {
return getUserEmail().orElseThrow();
public String username() {
return getUsername().orElseThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public AuthNzStack(Construct parent, DeployEnvironment deployEnv) {
.tempPasswordValidity(Duration.days(1)).build())
.signInCaseSensitive(false)
.signInAliases(SignInAliases.builder()
.preferredUsername(true)
.username(true)
.email(true).build())
.deviceTracking(DeviceTracking.builder()
.deviceOnlyRememberedOnUserPrompt(true)
Expand Down
4 changes: 4 additions & 0 deletions dataspray-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.5",
"@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0",
"@next/mdx": "^14.0.4",
"@types/mdx": "^2.0.10",
"@types/node": "^17.0.45",
"@types/node-fetch": "^2.6.9",
"formik": "^2.4.5",
Expand Down
Loading

0 comments on commit 435a375

Please sign in to comment.