mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
Merge branch 'master' of github.com:Unleash/unleash
This commit is contained in:
commit
75dfc96176
@ -22,6 +22,16 @@ unleash.start({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Additionally, you can trigger the admin interfact to prompt the user to sign in by configuring your middleware to return a `401` status on
|
||||||
|
protected routes. The response body must contain a `message` and a `path` used to redirect the user to the proper login route.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "You must be logged in to use Unleash",
|
||||||
|
"path": "/custom/login"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Examples on custom authentication hooks:
|
Examples on custom authentication hooks:
|
||||||
- [google-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/google-auth-hook.js)
|
- [google-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/google-auth-hook.js)
|
||||||
- [basic-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/basic-auth-hook.js)
|
- [basic-auth-hook.js](https://github.com/Unleash/unleash/blob/master/examples/basic-auth-hook.js)
|
||||||
|
@ -51,12 +51,20 @@ class FeatureToggleStore {
|
|||||||
.then(this.rowToFeature);
|
.then(this.rowToFeature);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFeatureName(name) {
|
hasFeature(name) {
|
||||||
return this.db
|
return this.db
|
||||||
.first('name')
|
.first('name', 'archived')
|
||||||
.from(TABLE)
|
.from(TABLE)
|
||||||
.where({ name })
|
.where({ name })
|
||||||
.then(n => !!n);
|
.then(row => {
|
||||||
|
if (!row) {
|
||||||
|
throw new NotFoundError('No feature toggle found');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: row.name,
|
||||||
|
archived: row.archived === 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getArchivedFeatures() {
|
getArchivedFeatures() {
|
||||||
|
@ -23,12 +23,7 @@ const handleErrors = (req, res, error) => {
|
|||||||
case NameExistsError:
|
case NameExistsError:
|
||||||
return res
|
return res
|
||||||
.status(403)
|
.status(403)
|
||||||
.json([
|
.json([{ msg: error.message }])
|
||||||
{
|
|
||||||
msg:
|
|
||||||
'A feature with this name already exists. Try re-activating it from the archive.',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.end();
|
.end();
|
||||||
case ValidationError:
|
case ValidationError:
|
||||||
return res
|
return res
|
||||||
@ -97,13 +92,15 @@ module.exports.router = function(config) {
|
|||||||
|
|
||||||
function validateUniqueName(req) {
|
function validateUniqueName(req) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
featureToggleStore.hasFeatureName(req.body.name).then(hasName => {
|
featureToggleStore
|
||||||
if (hasName) {
|
.hasFeature(req.body.name)
|
||||||
reject(new NameExistsError('Feature name already exist'));
|
.then(definition => {
|
||||||
} else {
|
const msg = definition.archived
|
||||||
resolve(req);
|
? 'An archived toggle with that name already exist'
|
||||||
}
|
: 'A toggle with that name already exist';
|
||||||
});
|
reject(new NameExistsError(msg));
|
||||||
|
})
|
||||||
|
.catch(() => resolve(req));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,56 @@ test('should require at least one strategy when creating a feature toggle', t =>
|
|||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be allowed to use new toggle name', t => {
|
||||||
|
t.plan(0);
|
||||||
|
const { request, base } = getSetup();
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'new.name' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to reuse active toggle name', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
featureToggleStore.addFeature({
|
||||||
|
name: 'ts',
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'ts' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(403)
|
||||||
|
.expect(res => {
|
||||||
|
t.true(res.body[0].msg === 'A toggle with that name already exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to reuse archived toggle name', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
featureToggleStore.addArchivedFeature({
|
||||||
|
name: 'ts.archived',
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'ts.archived' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(403)
|
||||||
|
.expect(res => {
|
||||||
|
t.true(
|
||||||
|
res.body[0].msg ===
|
||||||
|
'An archived toggle with that name already exist'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should require at least one strategy when updating a feature toggle', t => {
|
test('should require at least one strategy when updating a feature toggle', t => {
|
||||||
t.plan(0);
|
t.plan(0);
|
||||||
const { request, featureToggleStore, base } = getSetup();
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
"prometheus-gc-stats": "^0.5.0",
|
"prometheus-gc-stats": "^0.5.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"serve-favicon": "^2.3.0",
|
"serve-favicon": "^2.3.0",
|
||||||
"unleash-frontend": "^3.0.0-alpha.5",
|
"unleash-frontend": "^3.0.0-alpha.6",
|
||||||
"yallist": "^3.0.2",
|
"yallist": "^3.0.2",
|
||||||
"yargs": "^10.0.3"
|
"yargs": "^10.0.3"
|
||||||
},
|
},
|
||||||
|
12
test/fixtures/fake-feature-toggle-store.js
vendored
12
test/fixtures/fake-feature-toggle-store.js
vendored
@ -12,8 +12,18 @@ module.exports = () => {
|
|||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hasFeature: name => {
|
||||||
|
const toggle = _features.find(f => f.name === name);
|
||||||
|
const archived = _archive.find(f => f.name === name);
|
||||||
|
if (toggle) {
|
||||||
|
return Promise.resolve({ name, archived: false });
|
||||||
|
} else if (archived) {
|
||||||
|
return Promise.resolve({ name, archived: true });
|
||||||
|
} else {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
},
|
||||||
getFeatures: () => Promise.resolve(_features),
|
getFeatures: () => Promise.resolve(_features),
|
||||||
hasFeatureName: () => Promise.resolve(false),
|
|
||||||
addFeature: feature => _features.push(feature),
|
addFeature: feature => _features.push(feature),
|
||||||
getArchivedFeatures: () => Promise.resolve(_archive),
|
getArchivedFeatures: () => Promise.resolve(_archive),
|
||||||
addArchivedFeature: feature => _archive.push(feature),
|
addArchivedFeature: feature => _archive.push(feature),
|
||||||
|
@ -5775,9 +5775,9 @@ unique-temp-dir@^1.0.0:
|
|||||||
os-tmpdir "^1.0.1"
|
os-tmpdir "^1.0.1"
|
||||||
uid2 "0.0.3"
|
uid2 "0.0.3"
|
||||||
|
|
||||||
unleash-frontend@^3.0.0-alpha.5:
|
unleash-frontend@^3.0.0-alpha.6:
|
||||||
version "3.0.0-alpha.5"
|
version "3.0.0-alpha.6"
|
||||||
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.0.0-alpha.5.tgz#ef4c2bb9e24ba07465b1737098b92b6a036df282"
|
resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-3.0.0-alpha.6.tgz#439a5554a1169082b7ae19544002cad19409a27d"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^3.1.0"
|
debug "^3.1.0"
|
||||||
immutable "^3.8.1"
|
immutable "^3.8.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user