mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Merge branch 'master' into fix/json
This commit is contained in:
commit
8d5417d979
26
.github/ISSUE_TEMPLATE/SECURITY.md
vendored
26
.github/ISSUE_TEMPLATE/SECURITY.md
vendored
@ -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.
|
||||
@ -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',
|
||||
],
|
||||
|
||||
@ -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 });
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
96
docs/3.0.0-beta.x/guides/external-data.md
Normal file
96
docs/3.0.0-beta.x/guides/external-data.md
Normal 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();
|
||||
},
|
||||
};
|
||||
```
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -27,6 +27,8 @@ module.exports = () => {
|
||||
historyApiFallback: {
|
||||
index: '/admin/',
|
||||
},
|
||||
open: true,
|
||||
openPage: '/admin',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 |
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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({});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -91,6 +91,7 @@ exports.connect = (provider, query) => {
|
||||
const params = _.assign(profile, {
|
||||
provider: provider,
|
||||
role: defaultRole.id,
|
||||
confirmed: true,
|
||||
});
|
||||
|
||||
const createdUser = await strapi
|
||||
|
||||
@ -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.
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user