Merge branch 'master' into fix/json

This commit is contained in:
Alexandre BODIN 2019-11-18 10:11:34 +01:00 committed by GitHub
commit 8d5417d979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 287 additions and 68 deletions

View File

@ -1,26 +0,0 @@
---
name: 🛡 Security
about: Information on reporting security vulnerability
---
# Security Policy
## Supported Versions
As of November 2019 (and until this document is updated), only the v3.0.0-beta tags of Strapi are supported for updates. Any previous versions are currently not supported and users are advised to use them "at their own risk".
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities to
**[security@strapi.io](mailto:security@strapi.io)** or via the [Strapi Slack](https://slack.strapi.io).
When reporting a (suspected) security vulnerability via slack please reach out to any of the following Strapi employees directly:
- `@aureliengeorget`
- `@alexandre`
- `@lauriejim`
- `@soupette`
You will receive a response from us within 72 hours. If the issue is confirmed,
we will release a patch as soon as possible depending on complexity
but historically within a few days.

View File

@ -129,6 +129,7 @@ module.exports = {
'/3.0.0-beta.x/guides/deployment',
'/3.0.0-beta.x/guides/jwt-validation',
'/3.0.0-beta.x/guides/error-catching',
'/3.0.0-beta.x/guides/external-data',
'/3.0.0-beta.x/guides/slug',
'/3.0.0-beta.x/guides/webhooks',
],

View File

@ -67,7 +67,9 @@ module.exports = {
entities = await strapi.services.restaurant.find(ctx.query);
}
return entities.map(entity => sanitizeEntity(entity, { model }));
return entities.map(entity =>
sanitizeEntity(entity, { model: strapi.models.restaurant })
);
},
};
```
@ -90,7 +92,7 @@ module.exports = {
async findOne(ctx) {
const entity = await strapi.services.restaurant.findOne(ctx.params);
return sanitizeEntity(entity, { model });
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@ -142,7 +144,7 @@ module.exports = {
} else {
entity = await strapi.services.restaurant.create(ctx.request.body);
}
return sanitizeEntity(entity, { model });
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@ -177,7 +179,7 @@ module.exports = {
);
}
return sanitizeEntity(entity, { model });
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@ -200,7 +202,7 @@ module.exports = {
async delete(ctx) {
const entity = await strapi.services.restaurant.delete(ctx.params);
return sanitizeEntity(entity, { model });
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```

View File

@ -97,7 +97,7 @@ module.exports = {
if (files) {
// automatically uploads the files based on the entry and the model
await this.uploadFiles(entry, files, { model });
await this.uploadFiles(entry, files, { model: strapi.models.restaurant });
return this.findOne({ id: entry.id });
}
@ -125,7 +125,7 @@ module.exports = {
if (files) {
// automatically uploads the files based on the entry and the model
await this.uploadFiles(entry, files, { model });
await this.uploadFiles(entry, files, { model: strapi.models.restaurant });
return this.findOne({ id: entry.id });
}

View File

@ -89,7 +89,7 @@ Sort according to a specific field.
- ASC: `GET /users?_sort=email:ASC`
- DESC: `GET /users?_sort=email:DESC`
#### Sorting on multiple fileds
#### Sorting on multiple fields
- `GET /users?_sort=email:asc,dateField:desc`
- `GET /users?_sort=email:DESC,username:ASC`

View File

@ -0,0 +1,96 @@
# Fetching external data
This guide explains how to fetch data from an external service to use it in your app.
In this example we will see how to daily fetch Docker pull count to store the result in your database.
## Content Type settings
First, we need to create a Content Type, in this example we will call it `hit` with a `date` and `count` attribute.
Your Content Type should look like this:
**Path —** `./api/hit/models/Hit.settings.json`
```json
{
"connection": "default",
"collectionName": "hits",
"info": {
"name": "hit",
"description": ""
},
"options": {
"increments": true,
"timestamps": true,
"comment": ""
},
"attributes": {
"count": {
"type": "integer"
},
"date": {
"type": "date"
}
}
}
```
## Fetch the data
Now we will create a function that will be usable everywhere in your strapi application.
**Path —** `./config/functions/docker.js`
```js
const axios = require('axios');
module.exports = async () => {
const { data } = await axios.get(
'https://hub.docker.com/v2/repositories/strapi/strapi/'
);
console.log(data);
};
```
`data` contains all the data received from the Docker Hub API. What we want here is to add the `pull_count` value in your database.
## Create a `hit` entry
let's programmatically create the entry.
**Path —** `./config/functions/docker.js`
```js
const axios = require('axios');
module.exports = async () => {
const { data } = await axios.get(
'https://hub.docker.com/v2/repositories/strapi/strapi/'
);
await strapi.query('hit').create({
date: new Date(),
count: data.pull_count,
});
};
```
With this code, everytime this function is called it will fetch the docker repo's data and insert the current `pull_count` with the corresponding date in your Strapi database.
## Call the function
Here is how to call the function in your application `strapi.config.functions.docker()`
So let's execute this function everyday at 2am. For this we will use a [CRON tasks](../concepts/configurations.md#cron-tasks).
**Path —** `./config/functions/cron.js`
```js
module.exports = {
'0 2 * * *': () => {
strapi.config.functions.docker();
},
};
```

View File

@ -209,17 +209,42 @@ axios
.post('http://localhost:1337/auth/reset-password', {
code: 'privateCode',
password: 'myNewPassword',
passwordConfirmation: 'myNewPassword'
passwordConfirmation: 'myNewPassword',
})
.then(response => {
// Handle success.
console.log('Your user\'s password has been changed.');
console.log("Your user's password has been changed.");
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error);
});
});
```
### Email validation
This action send an email to a user with the link to confirm the user.
#### Usage
- email is the user email.
```js
import axios from 'axios';
// Request API.
axios
.post(`http://localhost:1337/auth/send-email-confirmation`, {
email: 'user@strapi.io',
})
.then(response => {
// Handle success.
console.log('Your user received an email');
})
.catch(error => {
// Handle error.
console.err('An error occured:', err);
});
```
## User object in Strapi context

View File

@ -47,7 +47,6 @@
"is-wsl": "^2.0.0",
"mini-css-extract-plugin": "^0.6.0",
"moment": "^2.24.0",
"open-browser-webpack-plugin": "^0.0.5",
"prop-types": "^15.7.2",
"react": "^16.9.0",
"react-copy-to-clipboard": "^5.0.1",

View File

@ -27,6 +27,8 @@ module.exports = () => {
historyApiFallback: {
index: '/admin/',
},
open: true,
openPage: '/admin',
},
};
};

View File

@ -44,7 +44,7 @@ async function askDbInfosAndTest(scope) {
dependencies: clientDependencies({ scope, client }),
};
await testDatabaseConnection({
return testDatabaseConnection({
scope,
configuration,
})
@ -67,6 +67,7 @@ async function askDbInfosAndTest(scope) {
});
}
)
.then(() => configuration)
.catch(err => {
if (retries < MAX_RETRIES - 1) {
console.log();
@ -88,8 +89,6 @@ async function askDbInfosAndTest(scope) {
`️⛔️ Could not connect to your database after ${MAX_RETRIES} tries. Try to check your database configuration an retry.`
);
});
return configuration;
}
return loop();

View File

@ -3,24 +3,16 @@
module.exports = ({ connection, env }) => {
// Production/Staging Template
if (['production', 'staging'].includes(env)) {
// All available settings (bookshelf and mongoose)
const settingsBase = {
client: connection.settings.client,
host: "${process.env.DATABASE_HOST || '127.0.0.1'}",
port: '${process.env.DATABASE_PORT || 27017}',
srv: '${process.env.DATABASE_SRV || false}',
database: "${process.env.DATABASE_NAME || 'strapi'}",
username: "${process.env.DATABASE_USERNAME || ''}",
password: "${process.env.DATABASE_PASSWORD || ''}",
ssl: '${process.env.DATABASE_SSL || false}',
};
// All available options (bookshelf and mongoose)
const optionsBase = {
ssl: '${process.env.DATABASE_SSL || false}',
authenticationDatabase:
"${process.env.DATABASE_AUTHENTICATION_DATABASE || ''}",
};
const optionsBase = {};
return {
defaultConnection: 'default',

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -18,6 +18,7 @@
"attribute.richtext": "Rich text",
"attribute.string": "String",
"attribute.text": "Text",
"attribute.uuid": "Uuid",
"button.attributes.add": "Add New Field",
"button.attributes.add.another": "Add Another Field",
"button.contentType.add": "Add a Content Type",
@ -169,8 +170,10 @@
"popUpForm.attributes.richtext.name": "Rich text",
"popUpForm.attributes.string.description": "Titles, names, paragraphs, list of names",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.description": "Descriptions, text paragraphs, articles ",
"popUpForm.attributes.text.description": "Descriptions, text paragraphs, articles",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.uuid.description": "Unique identifier",
"popUpForm.attributes.uuid.name": "Uuid",
"popUpForm.choose.attributes.header.title": "Add New Field",
"popUpForm.choose.attributes.header.subtitle.model": "Select a field for your content type",
"popUpForm.choose.attributes.header.subtitle.group": "Select a field for your group",

View File

@ -11,6 +11,7 @@ import relation from '../assets/icons/attributes/icon_relation.png';
import richtext from '../assets/icons/attributes/icon_text.png';
import string from '../assets/icons/attributes/icon_string.png';
import text from '../assets/icons/attributes/icon_text.png';
import uuid from '../assets/icons/attributes/icon_uuid.png';
const attributeIcons = {
boolean,
@ -26,6 +27,7 @@ const attributeIcons = {
richtext,
string,
text,
uuid,
};
export default attributeIcons;

View File

@ -358,12 +358,12 @@ const formatConnectionAggregator = function(fields, model, modelName) {
if (opts._q) {
// allow search param
return strapi.query(modelName).countSearch(opts);
return strapi.query(modelName, model.plugin).countSearch(opts);
}
return strapi.query(modelName).count(opts);
return strapi.query(modelName, model.plugin).count(opts);
},
totalCount(obj, options, context) {
return strapi.query(modelName).count({});
return strapi.query(modelName, model.plugin).count({});
},
},
};

View File

@ -59,7 +59,9 @@ const buildAssocResolvers = (model, name, { plugin }) => {
const { primaryKey, associations = [] } = model;
return associations.reduce((resolver, association) => {
return associations
.filter(association => model.attributes[association.alias].private !== true)
.reduce((resolver, association) => {
switch (association.nature) {
case 'oneToManyMorph': {
resolver[association.alias] = async obj => {

View File

@ -285,6 +285,20 @@
}
}
},
{
"method": "POST",
"path": "/auth/send-email-confirmation",
"handler": "Auth.sendEmailConfirmation",
"config": {
"policies": [],
"prefix": "",
"description": "Send a confirmation email to user",
"tag": {
"plugin": "users-permissions",
"name": "User"
}
}
},
{
"method": "GET",
"path": "/users",

View File

@ -597,4 +597,80 @@ module.exports = {
ctx.redirect(settings.email_confirmation_redirection || '/');
},
async sendEmailConfirmation(ctx) {
const pluginStore = await strapi.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
});
const params = _.assign(ctx.request.body);
if (!params.email) {
return ctx.badRequest('missing.email');
}
const isEmail = emailRegExp.test(params.email);
if (isEmail) {
params.email = params.email.toLowerCase();
} else {
return ctx.badRequest('wrong.email');
}
const user = await strapi.query('user', 'users-permissions').findOne({
email: params.email
});
if (user.confirmed) {
return ctx.badRequest('already.confirmed');
}
if (user.blocked) {
return ctx.badRequest('blocked.user');
}
const jwt = strapi.plugins['users-permissions'].services.jwt.issue(
_.pick(user.toJSON ? user.toJSON() : user, ['id'])
);
const settings = await pluginStore.get({ key: 'email' }).then(storeEmail => {
try {
return storeEmail['email_confirmation'].options;
} catch (err) {
return {};
}
});
settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.message, {
URL: new URL('/auth/email-confirmation', strapi.config.url).toString(),
USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']),
CODE: jwt
});
settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.object, {
USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']),
});
try {
await strapi.plugins['email'].services.email.send({
to: (user.toJSON ? user.toJSON() : user).email,
from:
settings.from.email && settings.from.name
? `"${settings.from.name}" <${settings.from.email}>`
: undefined,
replyTo: settings.response_email,
subject: settings.object,
text: settings.message,
html: settings.message
});
ctx.send({
email: (user.toJSON ? user.toJSON() : user).email,
sent: true
});
} catch (err) {
return ctx.badRequest(null, err);
}
},
};

View File

@ -53,6 +53,47 @@
"security": []
}
},
"/auth/send-email-confirmation": {
"post": {
"security": [],
"externalDocs": {
"description": "Find out more in the strapi's documentation",
"url": "https://strapi.io/documentation/guides/authentication.html#usage"
},
"responses": {
"200": {
"description": "Successfully sent email",
"content": {
"application/json": {
"email": {
"type": "string"
},
"sent": {
"type": "boolean"
}
}
}
}
},
"requestBody": {
"description": "",
"required": true,
"content": {
"application/json": {
"schema": {
"required": ["email"],
"properties": {
"email": {
"type": "string",
"minLength": 6
}
}
}
}
}
}
}
},
"/users-permissions/search/{id}": {
"get": {
"summary": "Retrieve a list of users by searching for their username or email",

View File

@ -91,6 +91,7 @@ exports.connect = (provider, query) => {
const params = _.assign(profile, {
provider: provider,
role: defaultRole.id,
confirmed: true,
});
const createdUser = await strapi

View File

@ -389,7 +389,9 @@ module.exports = {
};
// Retrieve roles
const roles = await strapi.query('role', 'users-permissions').find();
const roles = await strapi
.query('role', 'users-permissions')
.find({}, []);
// We have to know the difference to add or remove
// the permissions entries in the database.

View File

@ -12930,18 +12930,6 @@ only@~0.0.2:
resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=
open-browser-webpack-plugin@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/open-browser-webpack-plugin/-/open-browser-webpack-plugin-0.0.5.tgz#5e6dc6f8b8797331e212985de218572d84c0521f"
integrity sha1-Xm3G+Lh5czHiEphd4hhXLYTAUh8=
dependencies:
open "0.0.5"
open@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc"
integrity sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=
opencollective-postinstall@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89"