aws-samples / amazon-cognito-passwordless-auth

Passwordless authentication with Amazon Cognito: FIDO2 (WebAuthn, support for Passkeys), Magic Link, SMS OTP Step Up
Apache License 2.0
361 stars 62 forks source link

Prevent users from self-edit attributes #184

Open ItaiAlon opened 1 month ago

ItaiAlon commented 1 month ago

I tried to figure out how to block users from editing attributes by themself and found this topic: https://stackoverflow.com/questions/59090032/how-to-restrict-aws-cognito-users-from-taking-certain-actions

But I can't find a way to do that after the passwordless defined:

const passwordless = new Passwordless(stack, "Passwordless", { ... });

I tried to add that later, but not so sure how it works:

passwordless.userPool.addTrigger(cdk.aws_cognito.UserPoolOperation.POST_AUTHENTICATION,  ? );
ottokruse commented 1 month ago

Hi mate, that should work, pass the Lambda fn as 2nd argument. Similar to how we do it here: https://github.com/aws-samples/amazon-cognito-passwordless-auth/blob/4336c746dc91f4237b259effb0349e8e1db23b73/cdk/lib/cognito-passwordless.ts#L577-L580

ItaiAlon commented 1 month ago

If someone needs it:

import { Passwordless } from "amazon-cognito-passwordless-auth/cdk";

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class PasswordlessExt extends Passwordless {
  postAuthFn?: cdk.aws_lambda.IFunction;

  constructor(
    scope: Construct,
    id: string,
    props: ConstructorParameters<typeof Passwordless>[2] & {
      restrictSelfAttributesChange?: boolean;
      functionPropsExt?: {
        postAuth?: Partial<cdk.aws_lambda_nodejs.NodejsFunctionProps>;
      };
    }
  ) {
    super(scope, id, props);

    const restrictSelfAttributesChange =
      props.restrictSelfAttributesChange ?? false;
    if (restrictSelfAttributesChange) {
      this.postAuthFn = new cdk.aws_lambda_nodejs.NodejsFunction(
        this,
        `PostAuth${id}`,
        {
          entry: "post-authentication.js",
          runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
          architecture: cdk.aws_lambda.Architecture.ARM_64,
          bundling: {
            format: cdk.aws_lambda_nodejs.OutputFormat.ESM,
          },
          ...props.functionPropsExt?.postAuth,
          environment: {
            LOG_LEVEL: props.logLevel ?? "INFO",
            ...props.functionPropsExt?.postAuth?.environment,
          },
        }
      );

      this.userPool.addTrigger(
        cdk.aws_cognito.UserPoolOperation.POST_AUTHENTICATION,
        this.postAuthFn
      );
    }
  }
}

Now, in post-authentication.ts, you could control to what users have access:

import { PostAuthenticationTriggerHandler } from "aws-lambda";
import { logger } from "amazon-cognito-passwordless-auth/custom-auth/common";

export const handler: PostAuthenticationTriggerHandler = async (event) => {
  logger.info("Control access");
  logger.debug(JSON.stringify(event, null, 2));
  // Code here, error for stopping the user
  return event;
};
ItaiAlon commented 1 month ago

Don't sure how to get the x-amz-target from the headers

ItaiAlon commented 1 month ago

Is a user pool client a better option?

https://stackoverflow.com/questions/44013901/amazon-cognito-a-client-attempted-to-write-unauthorized-attribute https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#user-pool-settings-attribute-permissions-and-scopes

ottokruse commented 1 month ago

Don't sure how to get the x-amz-target from the headers

Where and why do you need to do that?

Is a user pool client a better option?

If you want to block ALL users from editing an attribute themselves yes --> then you should just set that attribute with ADMIN_UPDATE_USER_ATTRIBUTES only, and mark it as read-only on the app client that users use.

But e.g. if you want some users to be able to edit and others not, then you could code the right logic in a Lambda trigger as you were doing above

ItaiAlon commented 1 month ago

Where and why do you need to do that?

x-amz-target allowed to abort more actions (like writing, deleting user, etc.), but still read the user data or any other allowed option. I am not sure what the best practice is here.

If you want to block ALL users from editing an attribute themselves yes --> then you should just set that attribute with ADMIN_UPDATE_USER_ATTRIBUTES only, and mark it as read-only on the app client that users use.

I think the writing was only an example, I don't want users to operate any action without my permission, only read data or any action Passworless needs.