Skip to content

Commit

Permalink
fix: security and i18n installation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel910 committed Oct 19, 2019
1 parent 6e1f4d9 commit bb8aa0c
Show file tree
Hide file tree
Showing 20 changed files with 428 additions and 143 deletions.
10 changes: 8 additions & 2 deletions components/serverless-aws-cognito-user-pool/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ class ServerlessAwsCognito extends Component {
return this.state.output;
}

const { region = "us-east-1", name, tags = {}, appClients = [] } = inputs;
const {
region = "us-east-1",
name,
tags = {},
appClients = [],
allowSignup = false
} = inputs;
const passwordPolicy = Object.assign({}, defaultPasswordPolicy, inputs.passwordPolicy);

const cognito = new Cognito({ region });
Expand Down Expand Up @@ -91,7 +97,7 @@ class ServerlessAwsCognito extends Component {
const params = {
PoolName: name,
AdminCreateUserConfig: {
AllowAdminCreateUserOnly: false
AllowAdminCreateUserOnly: !allowSignup
},
AutoVerifiedAttributes: ["email"],
EmailConfiguration: {
Expand Down
2 changes: 1 addition & 1 deletion components/serverless-deploy/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Deploy extends Component {
const output = await template(inputs);

if (inputs.api) {
console.log(`\n🏁 Done! Here are some resources you will need to run your client apps:`);
console.log(`\n🎉 Done! Here are some resources you will need to run your client apps:`);
console.log(`----------`);

if (output.cdn) {
Expand Down
4 changes: 1 addition & 3 deletions examples/apps/admin/src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import typeformPlugins from "@webiny/app-typeform/admin";
import mailchimpPlugins from "@webiny/app-mailchimp/admin";
import formsPlugins from "@webiny/app-forms/admin/plugins";
import formsCmsPlugins from "@webiny/app-forms/page-builder/admin/plugins";
import install from "./install";

const plugins = [
fileUploadPlugin({}),
Expand All @@ -24,8 +23,7 @@ const plugins = [
cookiePolicyPlugins,
googleTagManagerPlugins,
typeformPlugins,
mailchimpPlugins,
install
mailchimpPlugins
];

export default plugins;
25 changes: 0 additions & 25 deletions examples/apps/admin/src/plugins/install.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import { ButtonPrimary } from "@webiny/ui/Button";
import { useSecurity } from "@webiny/app-security/hooks/useSecurity";

export default [
{
Expand All @@ -19,29 +18,5 @@ export default [
</div>
);
}
},
{
name: "install-i18n",
type: "install",
secure: true,
async isInstalled({ client }) {
return false;
},
render({ onInstalled }) {
return <I18nInstaller onInstalled={onInstalled} />;
}
}
];

const I18nInstaller = ({ onInstalled }) => {
const { user } = useSecurity();
return (
<div>
i18n installer{" "}
<span>
User: <strong>{user.email}</strong>
</span>
<ButtonPrimary onClick={onInstalled}>Install i18n</ButtonPrimary>
</div>
);
};
13 changes: 0 additions & 13 deletions packages/api-i18n/src/install/plugins/importData.js

This file was deleted.

26 changes: 0 additions & 26 deletions packages/api-i18n/src/install/plugins/index.js

This file was deleted.

7 changes: 0 additions & 7 deletions packages/api-i18n/src/install/plugins/setupEntities.js

This file was deleted.

11 changes: 9 additions & 2 deletions packages/api-i18n/src/plugins/graphql.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { emptyResolver } from "@webiny/commodo-graphql";
import { type PluginType } from "@webiny/api/types";
import { hasScope } from "@webiny/api-security";
import i18nLocale from "./graphql/I18NLocale";
import install from "./graphql/Install";

export default ([
{
Expand All @@ -28,6 +29,11 @@ export default ([
i18n: I18NMutation
}
type I18NBooleanResponse {
data: Boolean
error: I18NError
}
type I18NDeleteResponse {
data: Boolean
error: I18NError
Expand All @@ -49,7 +55,7 @@ export default ([
message: String
data: JSON
}
${install.typeDefs}
${i18nLocale.typeDefs}
`,
resolvers: merge(
Expand All @@ -61,7 +67,8 @@ export default ([
i18n: emptyResolver
}
},
i18nLocale.resolvers
i18nLocale.resolvers,
install.resolvers
)
},
security: {
Expand Down
28 changes: 28 additions & 0 deletions packages/api-i18n/src/plugins/graphql/Install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { install, isInstalled } from "./resolvers/install";

export default {
/* GraphQL */
typeDefs: `
input I18NInstallInput {
code: String!
}
extend type I18NQuery {
"Is I18N installed?"
isInstalled: I18NBooleanResponse
}
extend type I18NMutation {
"Install I18N"
install(data: I18NInstallInput!): I18NBooleanResponse
}
`,
resolvers: {
I18NQuery: {
isInstalled
},
I18NMutation: {
install
}
}
};
39 changes: 39 additions & 0 deletions packages/api-i18n/src/plugins/graphql/resolvers/install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ErrorResponse, Response } from "@webiny/api";
import { WithFieldsError } from "@webiny/commodo";
import { InvalidFieldsError } from "@webiny/commodo-graphql";

export const install = async (root: any, args: Object, context: Object) => {
const { I18NLocale } = context.models;

try {
const defaultLocale = new I18NLocale();
defaultLocale.code = args.data.code;
defaultLocale.default = true;
await defaultLocale.save();
} catch (e) {
if (e.code === WithFieldsError.VALIDATION_FAILED_INVALID_FIELDS) {
const attrError = InvalidFieldsError.from(e);
return new ErrorResponse({
code: attrError.code || WithFieldsError.VALIDATION_FAILED_INVALID_FIELDS,
message: attrError.message,
data: attrError.data
});
}
return new ErrorResponse({
code: e.code,
message: e.message,
data: e.data
});
}

return new Response(true);
};

export const isInstalled = async (root: any, args: Object, context: Object) => {
const { I18NLocale } = context.models;

// Check if at least 1 user exists in the system
const localeCount = await I18NLocale.count();

return new Response(localeCount > 0);
};
19 changes: 18 additions & 1 deletion packages/api-plugin-security-cognito/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default ({ region, userPoolId }) => {
{
name: "security-authentication-provider-cognito",
type: "security-authentication-provider",
async getUser({ idToken, SecurityUser }) {
async userFromToken({ idToken, SecurityUser }) {
const jwks = await getJWKs();
const { header } = jwt.decode(idToken, { complete: true });
const jwk = jwks.find(key => key.kid === header.kid);
Expand All @@ -65,6 +65,10 @@ export default ({ region, userPoolId }) => {

const user = await SecurityUser.findOne({ query: { email: token.email } });

if (!user) {
return null;
}

if (attrKeys.some(attr => token.hasOwnProperty(attr))) {
attrKeys.forEach(attr => {
user[updateAttributes[attr]] = token[attr];
Expand All @@ -75,6 +79,12 @@ export default ({ region, userPoolId }) => {

return user;
},
async getUser({ email }) {
return await cognito
.adminGetUser({ Username: email, UserPoolId: userPoolId })
.promise()
.catch(() => null);
},
async createUser({ data, user, permanent = false }) {
const params = {
UserPoolId: userPoolId,
Expand Down Expand Up @@ -146,6 +156,13 @@ export default ({ region, userPoolId }) => {
await cognito
.adminDeleteUser({ UserPoolId: userPoolId, Username: user.email })
.promise();
},
async countUsers() {
const { UserPool } = await cognito
.describeUserPool({ UserPoolId: userPoolId })
.promise();

return UserPool.EstimatedNumberOfUsers;
}
}
];
Expand Down
19 changes: 19 additions & 0 deletions packages/api-security/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@ Or if you prefer yarn:
```
yarn add @webiny/api-security
```

## Security installation
Installation mutation can be executed when certain conditions are met:
- there must be no existing users in the Webiny DB, or...
- there must be no existing users in the 3rd party auth provider, or...
- all of the above

If the above conditions are met, you can execute an `install` mutation
to create a new user with `full-access` role (a root user).

The logic behind user creation is built with the following scenarios in mind.
Say you want to create a new user with `[email protected]` email:
1) if a matching user is NOT FOUND in the Webiny DB, but is FOUND in auth provider,
a new local user is created. Auth provider user remains intact.
2) if a matching user is FOUND in the Webiny DB but is NOT FOUND in auth provider,
a new user is created on your auth provider, and the local user's data is updated
with the new firstName/lastName.
3) if a matching user is NOT FOUND anywhere, a new user is first created in the Webiny DB,
and after that, a new user is created in your auth provider.
14 changes: 13 additions & 1 deletion packages/api-security/src/plugins/graphql/Install.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,21 @@ export default {
isInstalled: SecurityBooleanResponse
}
type SecurityInstallResult {
"Webiny user was created"
user: Boolean
"Authentication provider user was created"
authUser: Boolean
}
type SecurityInstallResponse {
data: SecurityInstallResult
error: SecurityError
}
extend type SecurityMutation {
"Install Security"
install(data: SecurityInstallInput!): SecurityBooleanResponse
install(data: SecurityInstallInput!): SecurityInstallResponse
}
`,
resolvers: {
Expand Down
Loading

0 comments on commit bb8aa0c

Please sign in to comment.