Fix/#4513/ability to use a sub path behind a proxy (#5833)

* add possibility to use strapi on a non-root base url path

* fix documentation password form

* use server.url and admin.url in config

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* update doc proxy

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* move server.url location in config

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* refacto

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* add possibility to put relative urls

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* allow '/' as an admin url + refacto

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* update yarn.lock

Signed-off-by: Pierre Noël <petersg83@gmail.com>

* refacto

Signed-off-by: Pierre Noël <petersg83@gmail.com>

* Remove default proxy option

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>

* fix github provider

Signed-off-by: Pierre Noël <petersg83@gmail.com>

* fix github login

Signed-off-by: Pierre Noël <petersg83@gmail.com>

* Remove files that should be here

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>

Co-authored-by: Pierre Noël <pierre.noel@strapi.io>
Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Pierre Noël 2020-05-08 13:50:00 +02:00 committed by GitHub
parent 2b3d94d747
commit 57d7d876b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 345 additions and 829 deletions

View File

@ -139,8 +139,7 @@ import axios from 'axios';
axios
.post('http://localhost:1337/auth/forgot-password', {
email: 'user@strapi.io',
url:
'http:/localhost:1337/admin/plugins/users-permissions/auth/reset-password',
url: 'http:/localhost:1337/admin/plugins/users-permissions/auth/reset-password',
})
.then(response => {
// Handle success.

View File

@ -173,16 +173,12 @@ module.exports = ({ env }) => ({
| `host` | Host name | string | `localhost` |
| `port` | Port on which the server should be running. | integer | `1337` |
| `emitErrors` | Enable errors to be emitted to `koa` when they happen in order to attach custom logic or use error reporting services. | boolean | |
| `proxy` | Proxy configuration | Object | |
| `proxy.enabled` | Enable proxy support such as Apache or Nginx. | boolean | `false` |
| `proxy.ssl` | Enable proxy SSL support. | boolean | |
| `proxy.host` | Host name your proxy service uses for Strapi. | string | |
| `proxy.port` | Port that your proxy service accepts connections on. | integer | |
| `url` | Url of the server. Enable proxy support such as Apache or Nginx, example: `https://mywebsite.com/api`. Default value: `http://${host}:${port}`. | string | |
| `cron` | Cron configuration (powered by [`node-schedule`](https://github.com/node-schedule/node-schedule)) | Object | |
| `cron.enabled` | Enable or disable CRON tasks to schedule jobs at specific dates. | boolean | `false` |
| `admin` | Admin panel configuration | Object | |
| `admin.url` | Url of your admin panel. Default value: `/admin`. Note: If the url is relative, it will be concatenated with `url`. | string | `/admin` |
| `admin.autoOpen` | Enable or disabled administration opening on start. | boolean | `true` |
| `admin.path` | Allow to change the URL to access the admin panel. | string | `/admin` |
| `admin.watchIgnoreFiles` | Add custom files that should not be watched during development. See more [here](https://github.com/paulmillr/chokidar#path-filtering) (property `ignored`). | Array(string) | `[]`. |
| `admin.build` | Admin panel build configuration | Object | |
| `admin.build.backend` | URL that the admin panel and plugins will request | string | |

View File

@ -2,7 +2,7 @@
Upgrading your Strapi application to `v3.0.0-beta.18`.
**Make sure your server is not running until then end of the migration**
**Make sure your server is not running until the end of the migration**
## Upgrading your dependencies

View File

@ -2,7 +2,7 @@
Upgrading your Strapi application to `v3.0.0-beta.19`.
**Make sure your server is not running until then end of the migration**
**Make sure your server is not running until the end of the migration**
## Upgrading your dependencies

View File

@ -76,13 +76,9 @@ module.exports = strapi => {
try {
client = require(connection.settings.client);
} catch (err) {
strapi.log.error('The client `' + connection.settings.client + '` is not installed.');
strapi.log.error(
'The client `' + connection.settings.client + '` is not installed.'
);
strapi.log.error(
'You can install it with `$ npm install ' +
connection.settings.client +
' --save`.'
'You can install it with `$ npm install ' + connection.settings.client + ' --save`.'
);
strapi.stop();
}
@ -92,9 +88,7 @@ module.exports = strapi => {
client: connection.settings.client,
connection: {
host: _.get(connection.settings, 'host'),
user:
_.get(connection.settings, 'username') ||
_.get(connection.settings, 'user'),
user: _.get(connection.settings, 'username') || _.get(connection.settings, 'user'),
password: _.get(connection.settings, 'password'),
database: _.get(connection.settings, 'database'),
charset: _.get(connection.settings, 'charset'),
@ -106,10 +100,7 @@ module.exports = strapi => {
filename: _.get(connection.settings, 'filename', '.tmp/data.db'),
},
debug: _.get(connection.options, 'debug', false),
acquireConnectionTimeout: _.get(
connection.options,
'acquireConnectionTimeout'
),
acquireConnectionTimeout: _.get(connection.options, 'acquireConnectionTimeout'),
migrations: _.get(connection.options, 'migrations'),
useNullAsDefault: _.get(connection.options, 'useNullAsDefault'),
},
@ -121,26 +112,10 @@ module.exports = strapi => {
options.pool = {
min: _.get(connection.options, 'pool.min', 0),
max: _.get(connection.options, 'pool.max', 10),
acquireTimeoutMillis: _.get(
connection.options,
'pool.acquireTimeoutMillis',
2000
),
createTimeoutMillis: _.get(
connection.options,
'pool.createTimeoutMillis',
2000
),
idleTimeoutMillis: _.get(
connection.options,
'pool.idleTimeoutMillis',
30000
),
reapIntervalMillis: _.get(
connection.options,
'pool.reapIntervalMillis',
1000
),
acquireTimeoutMillis: _.get(connection.options, 'pool.acquireTimeoutMillis', 2000),
createTimeoutMillis: _.get(connection.options, 'pool.createTimeoutMillis', 2000),
idleTimeoutMillis: _.get(connection.options, 'pool.idleTimeoutMillis', 30000),
reapIntervalMillis: _.get(connection.options, 'pool.reapIntervalMillis', 1000),
createRetryIntervalMillis: _.get(
connection.options,
'pool.createRetryIntervalMillis',
@ -151,9 +126,7 @@ module.exports = strapi => {
// Resolve path to the directory containing the database file.
const fileDirectory = options.connection.filename
? path.dirname(
path.resolve(strapi.config.appPath, options.connection.filename)
)
? path.dirname(path.resolve(strapi.config.appPath, options.connection.filename))
: '';
switch (options.client) {
@ -180,12 +153,9 @@ module.exports = strapi => {
options.pool = {
...options.pool,
afterCreate: (conn, cb) => {
conn.query(
`SET SESSION SCHEMA '${options.connection.schema}';`,
err => {
cb(err, conn);
}
);
conn.query(`SET SESSION SCHEMA '${options.connection.schema}';`, err => {
cb(err, conn);
});
},
};
} else {
@ -227,9 +197,7 @@ module.exports = strapi => {
} catch (err) {
strapi.log.error('Impossible to use the `' + name + '` connection...');
strapi.log.warn(
'Be sure that your client `' +
name +
'` are in the same node_modules directory'
'Be sure that your client `' + name + '` are in the same node_modules directory'
);
strapi.log.error(err);
strapi.stop();

View File

@ -13,9 +13,14 @@ module.exports = async (ctx, next) => {
if (!ctx.session.documentation) {
const querystring = ctx.querystring ? `?${ctx.querystring}` : '';
return ctx.redirect(`${strapi.plugins.documentation.config['x-strapi-config'].path}/login${querystring}`);
return ctx.redirect(
`${strapi.config.server.url}${strapi.plugins.documentation.config['x-strapi-config'].path}/login${querystring}`
);
}
const isValid = strapi.plugins['users-permissions'].services.user.validatePassword(ctx.session.documentation, config.password);
const isValid = strapi.plugins['users-permissions'].services.user.validatePassword(
ctx.session.documentation,
config.password
);
if (!isValid) {
ctx.session.documentation = null;

View File

@ -18,11 +18,6 @@ const koaStatic = require('koa-static');
module.exports = {
getInfos: async ctx => {
try {
const prefix = _.get(
strapi.plugins,
['documentation', 'config', 'x-strapi-config', 'path'],
'/documentation'
);
const service = strapi.plugins.documentation.services.documentation;
const docVersions = service.retrieveDocumentationVersions();
const form = await service.retrieveFrontForm();
@ -30,7 +25,7 @@ module.exports = {
ctx.send({
docVersions,
currentVersion: service.getDocumentationVersion(),
prefix: `/${prefix}`.replace('//', '/'),
prefix: strapi.plugins.documentation.config['x-strapi-config'].path,
form,
});
} catch (err) {
@ -39,15 +34,7 @@ module.exports = {
},
async index(ctx, next) {
// Read layout file.
try {
const layout = fs.readFileSync(
path.resolve(__dirname, '..', 'public', 'index.html'),
'utf8'
);
const $ = cheerio.load(layout);
/**
* We don't expose the specs using koa-static or something else due to security reasons.
* That's why, we need to read the file localy and send the specs through it when we serve the Swagger UI.
@ -68,39 +55,16 @@ module.exports = {
try {
const documentation = fs.readFileSync(openAPISpecsPath, 'utf8');
// Remove previous Swagger configuration.
$('.custom-swagger-ui').remove();
// Set new Swagger configuration
$('body').append(`
<script class="custom-swagger-ui">
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
spec: ${JSON.stringify(JSON.parse(documentation))},
dom_id: '#swagger-ui',
docExpansion: "none",
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
`);
const layout = fs.readFileSync(
path.resolve(__dirname, '..', 'public', 'index.html'),
'utf8'
);
const filledLayout = _.template(layout)({
backendUrl: strapi.config.server.url,
spec: JSON.stringify(JSON.parse(documentation)),
});
try {
// Write the layout with the new Swagger configuration.
// fs.writeFileSync(layoutPath, $.html());
const layoutPath = path.resolve(
strapi.config.appPath,
'extensions',
@ -109,7 +73,7 @@ module.exports = {
'index.html'
);
await fs.ensureFile(layoutPath);
await fs.writeFile(layoutPath, $.html());
await fs.writeFile(layoutPath, filledLayout);
// Serve the file.
ctx.url = path.basename(`${ctx.url}/index.html`);
@ -140,15 +104,12 @@ module.exports = {
const { error } = ctx.query;
try {
const layout = fs.readFileSync(
path.join(__dirname, '..', 'public', 'login.html')
);
const $ = cheerio.load(layout);
const layout = fs.readFileSync(path.join(__dirname, '..', 'public', 'login.html'));
const filledLayout = _.template(layout)({
actionUrl: `${strapi.config.server.url}${strapi.plugins.documentation.config['x-strapi-config'].path}/login`,
});
const $ = cheerio.load(filledLayout);
$('form').attr(
'action',
`${strapi.plugins.documentation.config['x-strapi-config'].path}/login`
);
$('.error').text(_.isEmpty(error) ? '' : 'Wrong password...');
try {
@ -197,9 +158,10 @@ module.exports = {
})
.get();
const isValid = strapi.plugins[
'users-permissions'
].services.user.validatePassword(password, storedPassword);
const isValid = strapi.plugins['users-permissions'].services.user.validatePassword(
password,
storedPassword
);
let querystring = '?error=password';
if (isValid) {
@ -208,17 +170,13 @@ module.exports = {
}
ctx.redirect(
`${
strapi.plugins.documentation.config['x-strapi-config'].path
}${querystring}`
`${strapi.config.server.url}${strapi.plugins.documentation.config['x-strapi-config'].path}${querystring}`
);
},
regenerateDoc: async ctx => {
const service = strapi.plugins.documentation.services.documentation;
const documentationVersions = service
.retrieveDocumentationVersions()
.map(el => el.version);
const documentationVersions = service.retrieveDocumentationVersions().map(el => el.version);
const {
request: {
body: { version },
@ -254,10 +212,7 @@ module.exports = {
);
ctx.send({ ok: true });
} catch (err) {
ctx.badRequest(
null,
admin ? 'documentation.error.regenerateDoc' : 'An error occured'
);
ctx.badRequest(null, admin ? 'documentation.error.regenerateDoc' : 'An error occured');
} finally {
strapi.reload.isWatching = true;
}
@ -266,9 +221,7 @@ module.exports = {
deleteDoc: async ctx => {
strapi.reload.isWatching = false;
const service = strapi.plugins.documentation.services.documentation;
const documentationVersions = service
.retrieveDocumentationVersions()
.map(el => el.version);
const documentationVersions = service.retrieveDocumentationVersions().map(el => el.version);
const {
request: {
params: { version },
@ -318,14 +271,11 @@ module.exports = {
if (restrictedAccess && _.isEmpty(password)) {
return ctx.badRequest(
null,
admin
? 'users-permissions.Auth.form.error.password.provide'
: 'Please provide a password'
admin ? 'users-permissions.Auth.form.error.password.provide' : 'Please provide a password'
);
}
const isNewPassword =
!_.isEmpty(password) && password !== prevConfig.password;
const isNewPassword = !_.isEmpty(password) && password !== prevConfig.password;
if (isNewPassword && usersPermService.user.isHashed(password)) {
// Throw an error if the password selected by the user

View File

@ -18,9 +18,7 @@ module.exports = strapi => {
beforeInitialize() {
strapi.config.middleware.load.before.push('documentation');
initialRoutes.push(
..._.cloneDeep(strapi.plugins.documentation.config.routes)
);
initialRoutes.push(..._.cloneDeep(strapi.plugins.documentation.config.routes));
},
initialize() {
@ -31,22 +29,12 @@ module.exports = strapi => {
return route;
}
if (
route.handler === 'Documentation.index' ||
route.path === '/login'
) {
if (route.handler === 'Documentation.index' || route.path === '/login') {
route.config.policies = initialRoutes[index].config.policies;
}
// Set prefix to empty to be able to customise it.
if (
_.get(strapi.plugins, [
'documentation',
'config',
'x-strapi-config',
'path',
])
) {
if (_.get(strapi.plugins, ['documentation', 'config', 'x-strapi-config', 'path'])) {
route.config.prefix = '';
route.path = `/${strapi.plugins.documentation.config['x-strapi-config'].path}${route.path}`.replace(
'//',

File diff suppressed because one or more lines are too long

View File

@ -20,18 +20,18 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.login {
height: 100%;
background-color: #F6F9FC;
}
.login .login-form {
height: calc(100% - 70px);
padding: 68px 0 0;
text-align: center;
}
.login .login-form form {
position: relative;
max-width: 460px;
@ -42,7 +42,7 @@
box-shadow: 0px 2px 4px rgba(91, 107, 174, .15);
text-align: center;
}
.login .login-form form:before {
position: absolute;
content: '';
@ -77,7 +77,7 @@
text-align: left;
font-weight: 600;
}
.login .login-form form input {
outline: none;
width: calc(100% - 30px);
@ -120,7 +120,7 @@
<div class="col-lg-6 col-lg-offset-3 col-md-12">
<img alt="Strapi logo" class="logo" src="https://strapi.io/assets/images/logo_login.png">
<h2 class="sub-title">Enter the password to access to the documentation.</h2>
<form method="post" action="/documentation/login">
<form method="post" action="<%=actionUrl%>">
<span class="error">Wrong password...</span>
<label>Password</label>
<input type="password" name="password" placeholder="&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;&#x2022;">
@ -131,5 +131,5 @@
</div>
</section>
</div>
</body></html>
</body></html>

View File

@ -111,7 +111,7 @@ module.exports = strapi => {
strapi.plugins.graphql.config.playgroundAlways
) {
serverParams.playground = {
endpoint: strapi.plugins.graphql.config.endpoint,
endpoint: `${strapi.config.server.url}${strapi.plugins.graphql.config.endpoint}`,
shareEnabled: strapi.plugins.graphql.config.shareEnabled,
};
}

View File

@ -47,10 +47,7 @@ class PopUpForm extends React.Component {
UNSAFE_componentWillReceiveProps(nextProps) {
const { values } = nextProps;
if (
get(values, 'enabled') &&
get(values, 'enabled') !== get(this.props.values, 'enabled')
) {
if (get(values, 'enabled') && get(values, 'enabled') !== get(this.props.values, 'enabled')) {
this.setState({ enabled: get(values, 'enabled') });
}
}
@ -65,7 +62,7 @@ class PopUpForm extends React.Component {
case 'google':
return `${strapi.backendURL}/connect/google/callback`;
case 'github':
return get(this.props.values, 'redirect_uri', '');
return `${strapi.backendURL}/connect/github/callback`;
case 'microsoft':
return `${strapi.backendURL}/connect/microsoft/callback`;
case 'twitter':
@ -77,17 +74,13 @@ class PopUpForm extends React.Component {
default: {
const value = get(this.props.values, 'callback', '');
return startsWith(value, 'http')
? value
: `${strapi.backendURL}${value}`;
return startsWith(value, 'http') ? value : `${strapi.backendURL}${value}`;
}
}
};
generateRedirectURL = url => {
return startsWith(url, 'https://') ||
startsWith(url, 'http://') ||
this.state.isEditing
return startsWith(url, 'https://') || startsWith(url, 'http://') || this.state.isEditing
? url
: `${strapi.backendURL}${startsWith(url, '/') ? '' : '/'}${url}`;
};
@ -113,36 +106,26 @@ class PopUpForm extends React.Component {
handleFocus = () => this.setState({ isEditing: true });
renderForm = () => {
const {
dataToEdit,
didCheckErrors,
formErrors,
settingType,
values,
} = this.props;
const form = Object.keys(values.options || values || {}).reduce(
(acc, current) => {
const path =
settingType === 'email-templates' ? ['options', current] : [current];
const name = settingType === 'email-templates' ? 'options.' : '';
const { dataToEdit, didCheckErrors, formErrors, settingType, values } = this.props;
const form = Object.keys(values.options || values || {}).reduce((acc, current) => {
const path = settingType === 'email-templates' ? ['options', current] : [current];
const name = settingType === 'email-templates' ? 'options.' : '';
if (isObject(get(values, path)) && !isArray(get(values, path))) {
return Object.keys(get(values, path, {}))
.reduce((acc, curr) => {
acc.push(`${name}${current}.${curr}`);
if (isObject(get(values, path)) && !isArray(get(values, path))) {
return Object.keys(get(values, path, {}))
.reduce((acc, curr) => {
acc.push(`${name}${current}.${curr}`);
return acc;
}, [])
.concat(acc);
}
if (current !== 'icon' && current !== 'scope') {
acc.push(`${name}${current}`);
}
return acc;
}, [])
.concat(acc);
}
if (current !== 'icon' && current !== 'scope') {
acc.push(`${name}${current}`);
}
return acc;
},
[]
);
return acc;
}, []);
if (settingType === 'providers') {
return (
@ -166,11 +149,7 @@ class PopUpForm extends React.Component {
autoFocus={key === 0}
customBootstrapClass="col-md-12"
didCheckErrors={didCheckErrors}
errors={get(
formErrors,
[findIndex(formErrors, ['name', value]), 'errors'],
[]
)}
errors={get(formErrors, [findIndex(formErrors, ['name', value]), 'errors'], [])}
key={value}
label={{
id: `users-permissions.PopUpForm.Providers.${
@ -248,9 +227,7 @@ class PopUpForm extends React.Component {
placeholder={`users-permissions.PopUpForm.Email.${value}.placeholder`}
type={includes(value, 'email') ? 'email' : 'text'}
value={get(values, value)}
validations={
value !== 'options.response_email' ? { required: true } : {}
}
validations={value !== 'options.response_email' ? { required: true } : {}}
/>
))}
<div className="col-md-6" />
@ -278,9 +255,7 @@ class PopUpForm extends React.Component {
validations={{ required: true }}
value={get(values, value)}
inputStyle={
!includes(value, 'object')
? { height: '16rem', marginBottom: '-0.8rem' }
: {}
!includes(value, 'object') ? { height: '16rem', marginBottom: '-0.8rem' } : {}
}
/>
))}
@ -290,13 +265,7 @@ class PopUpForm extends React.Component {
render() {
const { display } = this.props.values;
const {
actionType,
dataToEdit,
isOpen,
onSubmit,
settingType,
} = this.props;
const { actionType, dataToEdit, isOpen, onSubmit, settingType } = this.props;
let header = <span>{dataToEdit}</span>;
@ -338,11 +307,7 @@ class PopUpForm extends React.Component {
onClick={this.context.unsetDataToEdit}
isSecondary
/>
<ButtonModal
message="form.button.done"
onClick={onSubmit}
type="submit"
/>
<ButtonModal message="form.button.done" onClick={onSubmit} type="submit" />
</section>
</ModalFooter>
</form>

View File

@ -27,7 +27,7 @@ module.exports = async () => {
icon: 'discord',
key: '',
secret: '',
callback: '/auth/discord/callback',
callback: `${strapi.config.server.url}/auth/discord/callback`,
scope: ['identify', 'email'],
},
facebook: {
@ -35,7 +35,7 @@ module.exports = async () => {
icon: 'facebook-square',
key: '',
secret: '',
callback: '/auth/facebook/callback',
callback: `${strapi.config.server.url}/auth/facebook/callback`,
scope: ['email'],
},
google: {
@ -43,7 +43,7 @@ module.exports = async () => {
icon: 'google',
key: '',
secret: '',
callback: '/auth/google/callback',
callback: `${strapi.config.server.url}/auth/google/callback`,
scope: ['email'],
},
github: {
@ -51,7 +51,7 @@ module.exports = async () => {
icon: 'github',
key: '',
secret: '',
redirect_uri: '/auth/github/callback',
callback: `${strapi.config.server.url}/auth/github/callback`,
scope: ['user', 'user:email'],
},
microsoft: {
@ -59,7 +59,7 @@ module.exports = async () => {
icon: 'windows',
key: '',
secret: '',
callback: '/auth/microsoft/callback',
callback: `${strapi.config.server.url}/auth/microsoft/callback`,
scope: ['user.read'],
},
twitter: {
@ -67,21 +67,21 @@ module.exports = async () => {
icon: 'twitter',
key: '',
secret: '',
callback: '/auth/twitter/callback',
callback: `${strapi.config.server.url}/auth/twitter/callback`,
},
instagram: {
enabled: false,
icon: 'instagram',
key: '',
secret: '',
callback: '/auth/instagram/callback',
callback: `${strapi.config.server.url}/auth/instagram/callback`,
},
vk: {
enabled: false,
icon: 'vk',
key: '',
secret: '',
callback: '/auth/vk/callback',
callback: `${strapi.config.server.url}/auth/vk/callback`,
scope: ['email'],
},
};
@ -145,17 +145,12 @@ module.exports = async () => {
}
if (!(await pluginStore.get({ key: 'advanced' }))) {
const host = strapi.config.get('server.host');
const port = strapi.config.get('server.port');
const uri = `http://${host}:${port}/admin`;
const value = {
unique_email: true,
allow_register: true,
email_confirmation: false,
email_confirmation_redirection: uri,
email_reset_password: uri,
email_confirmation_redirection: `${strapi.config.admin.url}/admin`,
email_reset_password: `${strapi.config.admin.url}/admin`,
default_role: 'authenticated',
};

View File

@ -55,9 +55,7 @@ module.exports = {
);
}
const query = {
provider,
};
const query = { provider };
// Check if the provided identifier is an email or not.
const isEmail = emailRegExp.test(params.identifier);
@ -251,20 +249,17 @@ module.exports = {
})
.get();
const [protocol, host] = strapi.config.url.split('://');
_.defaultsDeep(grantConfig, { server: { protocol, host } });
const [requestPath] = ctx.request.url.split('?');
const provider =
process.platform === 'win32' ? requestPath.split('\\')[2] : requestPath.split('/')[2];
const config = grantConfig[provider];
if (!_.get(config, 'enabled')) {
if (!_.get(grantConfig[provider], 'enabled')) {
return ctx.badRequest(null, 'This provider is disabled.');
}
// Ability to pass OAuth callback dynamically
grantConfig[provider].callback =
ctx.query && ctx.query.callback ? ctx.query.callback : grantConfig[provider].callback;
grantConfig[provider].callback = _.get(ctx, 'query.callback') || grantConfig[provider].callback;
grantConfig[provider].redirect_uri = `${strapi.config.server.url}/connect/${provider}/callback`;
return grant(grantConfig)(ctx, next);
},
@ -512,7 +507,7 @@ module.exports = {
settings.message = await strapi.plugins[
'users-permissions'
].services.userspermissions.template(settings.message, {
URL: new URL('/auth/email-confirmation', strapi.config.url).toString(),
URL: `${strapi.config.server.url}/auth/email-confirmation`,
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',
@ -652,7 +647,7 @@ module.exports = {
settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(
settings.message,
{
URL: new URL('/auth/email-confirmation', strapi.config.url).toString(),
URL: `${strapi.config.server.url}/auth/email-confirmation`,
USER: _.omit(user.toJSON ? user.toJSON() : user, [
'password',
'resetPasswordToken',

View File

@ -14,7 +14,7 @@
"dependencies": {
"@purest/providers": "^1.0.1",
"bcryptjs": "^2.4.3",
"grant-koa": "^4.6.0",
"grant-koa": "^5.0.1",
"immutable": "^3.8.2",
"jsonwebtoken": "^8.1.0",
"koa2-ratelimit": "^0.9.0",

View File

@ -55,10 +55,7 @@ exports.connect = (provider, query) => {
})
.get();
if (
_.isEmpty(_.find(users, { provider })) &&
!advanced.allow_register
) {
if (_.isEmpty(_.find(users, { provider })) && !advanced.allow_register) {
return resolve([
null,
[{ messages: [{ id: 'Auth.advanced.allow_register' }] }],
@ -95,9 +92,7 @@ exports.connect = (provider, query) => {
confirmed: true,
});
const createdUser = await strapi
.query('user', 'users-permissions')
.create(params);
const createdUser = await strapi.query('user', 'users-permissions').create(params);
return resolve([createdUser, null]);
} catch (err) {
@ -217,53 +212,41 @@ const getProfile = async (provider, query, callback) => {
},
});
request.post(
{
url: 'https://github.com/login/oauth/access_token',
form: {
client_id: grant.github.key,
client_secret: grant.github.secret,
code: access_token,
},
},
(err, res, body) => {
github
.query()
.get('user')
.auth(access_token)
.request((err, res, userbody) => {
if (err) {
return callback(err);
}
// This is the public email on the github profile
if (userbody.email) {
return callback(null, {
username: userbody.login,
email: userbody.email,
});
}
// Get the email with Github's user/emails API
github
.query()
.get('user')
.auth(body.split('&')[0].split('=')[1])
.request((err, res, userbody) => {
.get('user/emails')
.auth(access_token)
.request((err, res, emailsbody) => {
if (err) {
return callback(err);
}
// This is the public email on the github profile
if (userbody.email) {
return callback(null, {
username: userbody.login,
email: userbody.email,
});
}
// Get the email with Github's user/emails API
github
.query()
.get('user/emails')
.auth(body.split('&')[0].split('=')[1])
.request((err, res, emailsbody) => {
if (err) {
return callback(err);
}
return callback(null, {
username: userbody.login,
email: Array.isArray(emailsbody)
? emailsbody.find(email => email.primary === true).email
: null,
});
});
return callback(null, {
username: userbody.login,
email: Array.isArray(emailsbody)
? emailsbody.find(email => email.primary === true).email
: null,
});
});
}
);
});
break;
}
case 'microsoft': {

View File

@ -0,0 +1,62 @@
const _ = require('lodash');
const { getCommonBeginning } = require('./stringFormatting');
const getConfigUrls = (serverConfig, forAdminBuild = false) => {
// Defines serverUrl value
let serverUrl = _.get(serverConfig, 'url', '');
serverUrl = _.trim(serverUrl, '/ ');
if (typeof serverUrl !== 'string') {
throw new Error('Invalid server url config. Make sure the url is a string.');
}
if (serverUrl.startsWith('http')) {
try {
serverUrl = _.trim(new URL(serverConfig.url).toString(), '/');
} catch (e) {
throw new Error(
'Invalid server url config. Make sure the url defined in server.js is valid.'
);
}
} else if (serverUrl !== '') {
serverUrl = `/${serverUrl}`;
}
// Defines adminUrl value
let adminUrl = _.get(serverConfig, 'admin.url', '/admin');
adminUrl = _.trim(adminUrl, '/ ');
if (typeof adminUrl !== 'string') {
throw new Error('Invalid admin url config. Make sure the url is a non-empty string.');
}
if (adminUrl.startsWith('http')) {
try {
adminUrl = _.trim(new URL(adminUrl).toString(), '/');
} catch (e) {
throw new Error('Invalid admin url config. Make sure the url defined in server.js is valid.');
}
} else {
adminUrl = `${serverUrl}/${adminUrl}`;
}
// Defines adminPath value
let adminPath = adminUrl;
if (
serverUrl.startsWith('http') &&
adminUrl.startsWith('http') &&
new URL(adminUrl).origin === new URL(serverUrl).origin &&
!forAdminBuild
) {
adminPath = adminUrl.replace(getCommonBeginning(serverUrl, adminUrl), '');
adminPath = `/${_.trim(adminPath, '/')}`;
} else if (adminUrl.startsWith('http')) {
adminPath = new URL(adminUrl).pathname;
}
return {
serverUrl,
adminUrl,
adminPath,
};
};
module.exports = {
getConfigUrls,
};

View File

@ -15,7 +15,13 @@ const models = require('./models');
const policy = require('./policy');
const templateConfiguration = require('./templateConfiguration');
const { yup, formatYupErrors } = require('./validators');
const { nameToSlug, nameToCollectionName, escapeQuery } = require('./stringFormatting');
const {
nameToSlug,
nameToCollectionName,
getCommonBeginning,
escapeQuery,
} = require('./stringFormatting');
const { getConfigUrls } = require('./config');
module.exports = {
yup,
@ -32,5 +38,7 @@ module.exports = {
parseType,
nameToSlug,
nameToCollectionName,
getCommonBeginning,
getConfigUrls,
escapeQuery,
};

View File

@ -6,6 +6,20 @@ const nameToSlug = name => slugify(name, { separator: '-' });
const nameToCollectionName = name => slugify(name, { separator: '_' });
const getCommonBeginning = (str1 = '', str2 = '') => {
let common = '';
let index = 0;
while (index < str1.length && index < str2.length) {
if (str1[index] === str2[index]) {
common += str1[index];
index += 1;
} else {
break;
}
}
return common;
};
const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
return query
.split('')
@ -21,5 +35,6 @@ const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
module.exports = {
nameToSlug,
nameToCollectionName,
getCommonBeginning,
escapeQuery,
};

View File

@ -97,6 +97,14 @@ class Strapi {
logFirstStartupMessage() {
this.logStats();
let hostname = strapi.config.host;
if (
strapi.config.environment === 'development' &&
['127.0.0.1', '0.0.0.0'].includes(strapi.config.host)
) {
hostname = 'localhost';
}
console.log(chalk.bold('One more thing...'));
console.log(
chalk.grey('Create your first administrator 💻 by going to the administration panel at:')
@ -104,7 +112,13 @@ class Strapi {
console.log();
const addressTable = new CLITable();
addressTable.push([chalk.bold(this.config.admin.url)]);
if (this.config.admin.url.startsWith('http')) {
addressTable.push([chalk.bold(this.config.admin.url)]);
} else {
addressTable.push([
chalk.bold(`http://${hostname}:${strapi.config.port}${this.config.admin.url}`),
]);
}
console.log(`${addressTable.toString()}`);
console.log();
}
@ -112,16 +126,32 @@ class Strapi {
logStartupMessage() {
this.logStats();
let hostname = strapi.config.host;
if (
strapi.config.environment === 'development' &&
['127.0.0.1', '0.0.0.0'].includes(strapi.config.host)
) {
hostname = 'localhost';
}
console.log(chalk.bold('Welcome back!'));
if (this.config.serveAdminPanel === true) {
console.log(chalk.grey('To manage your project 🚀, go to the administration panel at:'));
console.log(chalk.bold(this.config.admin.url));
if (this.config.admin.url.startsWith('http')) {
console.log(chalk.bold(this.config.admin.url));
} else {
console.log(chalk.bold(`http://${hostname}:${strapi.config.port}${this.config.admin.url}`));
}
console.log();
}
console.log(chalk.grey('To access the server ⚡️, go to:'));
console.log(chalk.bold(this.config.url));
if (this.config.admin.url.startsWith('http')) {
console.log(chalk.bold(this.config.server.url));
} else {
console.log(chalk.bold(`http://${hostname}:${strapi.config.port}${this.config.server.url}`));
}
console.log();
}

View File

@ -3,6 +3,8 @@
const { green } = require('chalk');
// eslint-disable-next-line node/no-extraneous-require
const strapiAdmin = require('strapi-admin');
const { getConfigUrls } = require('strapi-utils');
const loadConfiguration = require('../core/app-configuration');
const addSlash = require('../utils/addSlash');
/**
@ -12,8 +14,7 @@ module.exports = async ({ clean, optimization }) => {
const dir = process.cwd();
const config = loadConfiguration(dir);
const adminPath = config.get('server.admin.path', '/admin');
const adminBackend = config.get('server.admin.build.backend', '/');
const { serverUrl, adminPath } = getConfigUrls(config.get('server'), true);
console.log(`Building your admin UI with ${green(config.environment)} configuration ...`);
@ -28,7 +29,7 @@ module.exports = async ({ clean, optimization }) => {
env: 'production',
optimize: optimization,
options: {
backend: adminBackend,
backend: serverUrl,
publicPath: addSlash(adminPath),
},
})

View File

@ -21,7 +21,7 @@ module.exports = async (plugins, { deleteFiles }) => {
const loader = ora();
const dir = process.cwd();
const pluginArgs = plugins.map((name) => `strapi-plugin-${name}`);
const pluginArgs = plugins.map(name => `strapi-plugin-${name}`);
try {
// verify should rebuild before removing the pacakge

View File

@ -1,5 +1,6 @@
// eslint-disable-next-line node/no-extraneous-require
const strapiAdmin = require('strapi-admin');
const { getConfigUrls } = require('strapi-utils');
const loadConfiguration = require('../core/app-configuration');
const addSlash = require('../utils/addSlash');
@ -9,14 +10,10 @@ module.exports = async function() {
const config = loadConfiguration(dir);
const port = config.get('server.port', 1337);
const host = config.get('server.host', 'localhost');
const { serverUrl, adminPath } = getConfigUrls(config.get('server'), true);
const adminPort = config.get('server.admin.port', 8000);
const adminHost = config.get('server.admin.host', 'localhost');
const adminBackend = config.get('server.admin.build.backend', `http://${host}:${port}`);
const adminPath = config.get('server.admin.path', '/admin');
const adminWatchIgnoreFiles = config.get('server.admin.watchIgnoreFiles', []);
strapiAdmin.watchAdmin({
@ -24,7 +21,7 @@ module.exports = async function() {
port: adminPort,
host: adminHost,
options: {
backend: adminBackend,
backend: serverUrl,
publicPath: addSlash(adminPath),
watchIgnoreFiles: adminWatchIgnoreFiles,
},

View File

@ -37,7 +37,6 @@ const defaultConfig = {
server: {
host: process.env.HOST || os.hostname() || 'localhost',
port: process.env.PORT || 1337,
proxy: { enabled: false },
cron: { enabled: false },
admin: { autoOpen: false },
},

View File

@ -1,9 +1,9 @@
'use strict';
const _ = require('lodash');
const { getConfigUrls } = require('strapi-utils');
const { createCoreApi } = require('../core-api');
const getURLFromSegments = require('../utils/url-from-segments');
const getKind = obj => obj.kind || 'collectionType';
@ -182,30 +182,15 @@ module.exports = function(strapi) {
}, {});
// default settings
const port = strapi.config.get('server.port');
const host = strapi.config.get('server.host');
strapi.config.port = strapi.config.get('server.port') || strapi.config.port;
strapi.config.host = strapi.config.get('server.host') || strapi.config.host;
let hostname = host;
if (strapi.config.environment === 'development' && ['127.0.0.1', '0.0.0.0'].includes(host)) {
hostname = 'localhost';
}
const { serverUrl, adminUrl, adminPath } = getConfigUrls(strapi.config.get('server'));
// proxy settings
const proxy = strapi.config.get('server.proxy', {});
// check if proxy is enabled and construct url
strapi.config.url = proxy.enabled
? getURLFromSegments({
hostname: proxy.host,
port: proxy.port,
ssl: proxy.ssl,
})
: getURLFromSegments({
hostname,
port,
});
const adminPath = strapi.config.get('server.admin.path', 'admin');
strapi.config.server = strapi.config.server || {};
strapi.config.server.url = serverUrl;
strapi.config.admin.url = adminUrl;
strapi.config.admin.path = adminPath;
// check if we should serve admin panel
const shouldServeAdmin = strapi.config.get(
@ -216,6 +201,4 @@ module.exports = function(strapi) {
if (!shouldServeAdmin) {
strapi.config.serveAdminPanel = false;
}
strapi.config.admin.url = new URL(adminPath, strapi.config.url).toString();
};

View File

@ -74,12 +74,10 @@ module.exports = strapi => {
if (!strapi.config.serveAdminPanel) return;
const basename = strapi.config.get('server.admin.path', '/admin');
const buildDir = path.resolve(strapi.dir, 'build');
// Serve admin assets.
strapi.router.get(
`${basename}/*`,
`${strapi.config.admin.path}/*`,
async (ctx, next) => {
ctx.url = path.basename(ctx.url);
await next();
@ -91,7 +89,7 @@ module.exports = strapi => {
})
);
strapi.router.get(`${basename}*`, ctx => {
strapi.router.get(`${strapi.config.admin.path}*`, ctx => {
ctx.type = 'html';
ctx.body = fs.createReadStream(path.join(buildDir + '/index.html'));
});

View File

@ -1,5 +1,5 @@
module.exports = path => {
if (typeof path !== 'string') throw new Error('admin.path must be a string');
if (typeof path !== 'string') throw new Error('admin.url must be a string');
if (path === '' || path === '/') return '/';
if (path[0] != '/') path = '/' + path;

View File

@ -49,11 +49,7 @@ function executeNodeScript(scriptPath, url) {
child.on('close', code => {
if (code !== 0) {
console.log();
console.log(
chalk.red(
'The script specified as BROWSER environment variable failed.'
)
);
console.log(chalk.red('The script specified as BROWSER environment variable failed.'));
console.log(`${chalk.cyan(scriptPath)} exited with code ${code}.`);
console.log();
return;
@ -68,21 +64,17 @@ function startBrowserProcess(browser, url) {
// Chrome with AppleScript. This lets us reuse an
// existing tab when possible instead of creating a new one.
const shouldTryOpenChromeWithAppleScript =
process.platform === 'darwin' &&
(typeof browser !== 'string' || browser === OSX_CHROME);
process.platform === 'darwin' && (typeof browser !== 'string' || browser === OSX_CHROME);
if (shouldTryOpenChromeWithAppleScript) {
try {
// Try our best to reuse existing tab
// on OS X Google Chrome with AppleScript
execSync('ps cax | grep "Google Chrome"');
execSync(
`osascript resources/openChrome.applescript "${encodeURI(url)}"`,
{
cwd: __dirname,
stdio: 'ignore',
}
);
execSync(`osascript resources/openChrome.applescript "${encodeURI(url)}"`, {
cwd: __dirname,
stdio: 'ignore',
});
return true;
} catch (err) {
strapi.log.error('Failed to open Google Chrome with AppleScript');
@ -120,9 +112,7 @@ async function pingDashboard(url, multipleTime = false) {
// Only display once.
if (!multipleTime) {
this.log.warn(
`⚠️ The admin panel is unavailable... Impossible to open it in the browser.`
);
this.log.warn(`⚠️ The admin panel is unavailable... Impossible to open it in the browser.`);
}
}
}
@ -132,7 +122,10 @@ async function pingDashboard(url, multipleTime = false) {
* true if it opened a browser or ran a node.js script, otherwise false.
*/
async function openBrowser() {
const url = this.config.admin.url;
let url = this.config.admin.url;
if (!url.startsWith('http')) {
url = `http://${strapi.config.host}:${strapi.config.port}${this.config.admin.url}`;
}
// Ping the dashboard to ensure it's available.
await pingDashboard.call(this, url);

View File

@ -8842,21 +8842,21 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
grant-koa@^4.6.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/grant-koa/-/grant-koa-4.7.0.tgz#4c7b2e23afe492988cd6a5874d5592f6a813ca50"
integrity sha512-dvu6FgtFOXncHM3faiJKehMN+4cRajb68E0FtmT5uMUUHZ4LZifv6ihANH9l0pvl0t+7UjEIosMrhnE73bhjpQ==
grant-koa@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/grant-koa/-/grant-koa-5.0.1.tgz#6606b762d999855ae3bc3b2ecc222cf21154324f"
integrity sha512-/vemHxSbyw8PNB04zqMMSSOiVhjercOZj1LL0AFIJGFZvdzBDiwmQEX2ioOmeaOBCLVCAdhQ0EfkEaln540Agg==
dependencies:
grant "4.7.0"
grant "5.0.1"
grant@4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/grant/-/grant-4.7.0.tgz#ab879a38ced7860df668db6c66012aa02402f49b"
integrity sha512-QGPjCYDrBnb/OIiTRxbK3TnNOE6Ycgfc/GcgPzI4vyNIr+b7yisEexYp7VM74zj6bxr+mDTzfGONRLzzsVPJIA==
grant@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/grant/-/grant-5.0.1.tgz#86d8c970fc54e860b5ad97a6544d3c7bc87c2fc2"
integrity sha512-ZYlnlzMA9f9SielWBDSwGZ34XcB2BL8wktDE5T2OcM13T7GFirjLWI0oasqrigPcLetazRbCUIBqqsy2Mvl8Zw==
dependencies:
qs "^6.9.1"
request-compose "^1.2.1"
request-oauth "0.0.3"
qs "^6.9.3"
request-compose "^2.0.0"
request-oauth "^1.0.0"
graphlib@^2.1.1, graphlib@^2.1.5:
version "2.1.8"
@ -13104,16 +13104,16 @@ nwsapi@^2.0.7:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
oauth-sign@^0.8.2, oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
oauth-sign@~0.9.0:
oauth-sign@^0.9.0, oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -14836,7 +14836,7 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.4.0, qs@^6.5.1, qs@^6.5.2, qs@^6.9.1:
qs@^6.4.0, qs@^6.5.2:
version "6.9.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
@ -15774,19 +15774,19 @@ reportback@^2.0.2:
captains-log "^2.0.2"
switchback "^2.0.1"
request-compose@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/request-compose/-/request-compose-1.2.2.tgz#9bd2ec8c8f52d154cff3302f637d81b73b371d49"
integrity sha512-aXA7l8fGSp4ZUeM/7pcMh3rvor7vKMptT7kPnTQfbzLOW4wy/hJgMzVhz9l2YZYgsbVj4ZgAc9JMI02V4nZgsA==
request-compose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/request-compose/-/request-compose-2.0.0.tgz#064121f8c52087d33c6b487eb14f32bc4af99548"
integrity sha512-+R9ka4bd3PH7A6B4iHn5SwAesGOUyPP2uCO6YJg6Ct/odLO0h9a7eWDPLXF1fws2us8sboGY0TtBl1wTpx60OA==
request-oauth@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/request-oauth/-/request-oauth-0.0.3.tgz#b3ea1ff857b9add3b0d49e42993a88e256d791ab"
integrity sha512-q7WdJlpIcPaIDac0FZSy/yH37FO3UkUuPDIsiLALiLjuC6vzvuKIU14YIC3Lm0wtQRXS9GqvSo/fCYj8n75xSw==
request-oauth@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/request-oauth/-/request-oauth-1.0.0.tgz#c8ebe047fb4ff4d5aa4ddb33e98b9ac74c625674"
integrity sha512-wsDzIq1Qu2itLDlcpFph8xh5Q+FVyUj4os5zdQTlZL/JvZYF/qOyaawVPsxxhDG4QwCB3tzSFprj6dkjqR+e8w==
dependencies:
oauth-sign "^0.8.2"
qs "^6.5.1"
uuid "^3.2.1"
oauth-sign "^0.9.0"
qs "^6.9.3"
uuid "^3.4.0"
request-progress@0.3.1:
version "0.3.1"
@ -18692,7 +18692,7 @@ uuid@3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3:
uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==