Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cognito): user pool resource server #11118

Merged
merged 14 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,48 @@ pool.addClient('app-client', {
});
```

### Resource Servers
iRoachie marked this conversation as resolved.
Show resolved Hide resolved

In your application you may need to attach custom permissions when using oAuth. Resource Servers allow us to provide custom scopes that we can attach to an app client. For example, you might be dealing with user data, and want to give different permissions to different clients.
iRoachie marked this conversation as resolved.
Show resolved Hide resolved

```ts
const pool = new cognito.UserPool(this, 'Pool');

pool.addResourceServer('ResourceServer', {
identifier: 'users',
scopes: [
{
scopeName: 'read',
scopeDescription: 'Read-only access',
},
{
scopeName: '*',
scopeDescription: 'Full access',
},
]
});

pool.addClient('read-only-client', {
// ...
oAuth: {
flows: {
clientCredentials: true,
},
scopes: [OAuthScope.custom('users/read')],
},
});

pool.addClient('full-access-client', {
// ...
oAuth: {
flows: {
clientCredentials: true,
},
scopes: [OAuthScope.custom('users/*')],
},
});
```

### Domains

After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './user-pool';
export * from './user-pool-attr';
export * from './user-pool-client';
export * from './user-pool-domain';
export * from './user-pool-idp';
export * from './user-pool-idp';
export * from './user-pool-resource-server';
94 changes: 94 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { IResource, Resource } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnUserPoolResourceServer } from './cognito.generated';
import { IUserPool } from './user-pool';

/**
* Represents a Cognito user pool resource server
*/
export interface IUserPoolResourceServer extends IResource {
/**
* Resource server id
* @attribute
*/
readonly userPoolResourceServerId: string;
}

/**
* Options to create a scope for a UserPoolResourceServer
*/
export interface ResourceServerScope {
/**
* The name of the scope
*/
readonly scopeName: string;

/**
* A description of the scope.
*/
readonly scopeDescription: string;
}


/**
* Options to create a UserPoolResourceServer
*/
export interface UserPoolResourceServerOptions {
/**
* A unique resource server identifier for the resource server.
*/
readonly identifier: string;

/**
* A friendly name for the resource server.
* @default - will use the identifier
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly userPoolResourceServerName?: string;

/**
* Oauth scopes
* @default - No scopes will be added
*/
readonly scopes?: ResourceServerScope[];
}

/**
* Properties for the UserPoolResourceServer construct
*/
export interface UserPoolResourceServerProps extends UserPoolResourceServerOptions {
/**
* The user pool to add this resource server to
*/
readonly userPool: IUserPool;
}

/**
* Defines a User Pool OAuth2.0 Resource Server
*/
export class UserPoolResourceServer extends Resource implements IUserPoolResourceServer {
/**
* Import a user pool resource client given its id.
*/
public static fromUserPoolResourceServerId(scope: Construct, id: string, userPoolResourceServerId: string): IUserPoolResourceServer {
class Import extends Resource implements IUserPoolResourceServer {
public readonly userPoolResourceServerId = userPoolResourceServerId;
}

return new Import(scope, id);
}

public readonly userPoolResourceServerId: string;

constructor(scope: Construct, id: string, props: UserPoolResourceServerProps) {
super(scope, id);

const resource = new CfnUserPoolResourceServer(this, 'Resource', {
identifier: props.identifier,
name: props.userPoolResourceServerName ?? props.identifier,
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
scopes: props.scopes,
userPoolId: props.userPool.userPoolId,
});

this.userPoolResourceServerId = resource.ref;
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
}
}
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user-
import { UserPoolClient, UserPoolClientOptions } from './user-pool-client';
import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain';
import { IUserPoolIdentityProvider } from './user-pool-idp';
import { UserPoolResourceServer, UserPoolResourceServerOptions } from './user-pool-resource-server';

/**
* The different ways in which users of this pool can sign up or sign in.
Expand Down Expand Up @@ -600,6 +601,12 @@ export interface IUserPool extends IResource {
*/
addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain;

/**
* Add a new resource server to this user pool.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-resource-servers.html
*/
addResourceServer(id: string, options: UserPoolResourceServerOptions): UserPoolResourceServer;

/**
* Register an identity provider with this user pool.
*/
Expand All @@ -625,6 +632,13 @@ abstract class UserPoolBase extends Resource implements IUserPool {
});
}

public addResourceServer(id: string, options: UserPoolResourceServerOptions): UserPoolResourceServer {
return new UserPoolResourceServer(this, id, {
userPool: this,
...options,
});
}

public registerIdentityProvider(provider: IUserPoolIdentityProvider) {
this.identityProviders.push(provider);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"Resources": {
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{
"Name": "verified_phone_number",
"Priority": 1
},
{
"Name": "verified_email",
"Priority": 2
}
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
"EmailVerificationMessage": "The verification code to your new account is {####}",
"EmailVerificationSubject": "Verify your new account",
"SmsVerificationMessage": "The verification code to your new account is {####}",
"UserPoolName": "MyUserPool",
"VerificationMessageTemplate": {
"DefaultEmailOption": "CONFIRM_WITH_CODE",
"EmailMessage": "The verification code to your new account is {####}",
"EmailSubject": "Verify your new account",
"SmsMessage": "The verification code to your new account is {####}"
}
}
},
"myuserpoolmyserver50C4D8E9": {
"Type": "AWS::Cognito::UserPoolResourceServer",
"Properties": {
"Identifier": "users",
"Name": "internal-users",
"UserPoolId": {
"Ref": "myuserpool01998219"
},
"Scopes": [
{
"ScopeDescription": "read only",
"ScopeName": "read"
}
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { App, Stack } from '@aws-cdk/core';
import { UserPool } from '../lib';

const app = new App();
const stack = new Stack(app, 'integ-user-pool-resource-server');

iRoachie marked this conversation as resolved.
Show resolved Hide resolved
const userPool = new UserPool(stack, 'myuserpool', {
userPoolName: 'MyUserPool',
});

userPool.addResourceServer('myserver', {
identifier: 'users',
userPoolResourceServerName: 'internal-users',
scopes: [
{
scopeName: 'read',
scopeDescription: 'read only',
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import '@aws-cdk/assert/jest';
import { Stack } from '@aws-cdk/core';
import { UserPool, UserPoolResourceServer } from '../lib';

describe('User Pool Resource Server', () => {
test('default setup', () => {
// GIVEN
const stack = new Stack();
const pool = new UserPool(stack, 'Pool');

// WHEN
new UserPoolResourceServer(stack, 'Server', {
userPool: pool,
identifier: 'users',
});

//THEN
expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', {
Identifier: 'users',
Name: 'users',
UserPoolId: stack.resolve(pool.userPoolId),
});
});

test('can assign a custom name', () => {
// GIVEN
const stack = new Stack();
const pool = new UserPool(stack, 'Pool');

// WHEN
new UserPoolResourceServer(stack, 'Server', {
userPoolResourceServerName: 'internal-users',
userPool: pool,
identifier: 'users',
});

//THEN
expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', {
Identifier: 'users',
Name: 'internal-users',
});
});

test('can assign scopes', () => {
// GIVEN
const stack = new Stack();
const pool = new UserPool(stack, 'Pool');

// WHEN
new UserPoolResourceServer(stack, 'Server', {
userPool: pool,
identifier: 'users',
scopes: [
{
scopeName: 'read',
scopeDescription: 'read only access',
},
],
});

//THEN
expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', {
Scopes: [
{
ScopeDescription: 'read only access',
ScopeName: 'read',
},
],
});
});
});
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-cognito/test/user-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,36 @@ describe('User Pool', () => {
});
});

test('addResourceServer', () => {
// GIVEN
const stack = new Stack();

// WHEN
const userpool = new UserPool(stack, 'Pool');
userpool.addResourceServer('ResourceServer', {
identifier: 'users',
scopes: [
{
scopeName: 'read',
scopeDescription: 'Read-only access',
},
],
});

// THEN
expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolResourceServer', {
Identifier: 'users',
Name: 'users',
UserPoolId: stack.resolve(userpool.userPoolId),
Scopes: [
{
ScopeDescription: 'Read-only access',
ScopeName: 'read',
},
],
});
});

test('addDomain', () => {
// GIVEN
const stack = new Stack();
Expand Down