Strip personally identifiable information from user table for unauthorized users.
- add a config option to explicitly enumerate pii fields beyond email - in query controller, strip pii of user table results before sending out the door.
This commit is contained in:
committed by
Florent Vilmart
parent
a270632570
commit
01b05b060f
503
spec/UserPII.spec.js
Normal file
503
spec/UserPII.spec.js
Normal file
@@ -0,0 +1,503 @@
|
||||
'use strict';
|
||||
|
||||
const Parse = require('parse/node');
|
||||
const request = require('request-promise');
|
||||
|
||||
// const Config = require('../src/Config');
|
||||
|
||||
const EMAIL = 'foo@bar.com';
|
||||
const ZIP = '10001';
|
||||
const SSN = '999-99-9999';
|
||||
|
||||
describe('Personally Identifiable Information', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(done => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
.then(loggedInUser => user = loggedInUser)
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||
.then(() => user
|
||||
.set('email', EMAIL)
|
||||
.set('zip', ZIP)
|
||||
.set('ssn', SSN)
|
||||
.save())
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
})
|
||||
.fail(e => {
|
||||
done.fail(JSON.stringify(e));
|
||||
})
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
describe('with configured sensitive fields', () => {
|
||||
beforeEach((done) => {
|
||||
reconfigureServer({ userSensitiveFields: ['ssn', 'zip'] })
|
||||
.then(() => done());
|
||||
});
|
||||
|
||||
it('should be able to get own PII via API with object', (done) => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
}, e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not be able to get PII via API with object', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch().then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get PII via API with object using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => {
|
||||
const userObj = new (Parse.Object.extend(Parse.User));
|
||||
userObj.id = user.id;
|
||||
userObj.fetch({ useMasterKey: true }).then(
|
||||
fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
}, e => console.error('error', e))
|
||||
.done(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Find', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Find', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first()
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Find using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.first({ useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get own PII via API with Get', (done) => {
|
||||
new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not get PII via API with Get', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id)
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(undefined);
|
||||
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get PII via API with Get using master key', (done) => {
|
||||
Parse.User.logOut()
|
||||
.then(() => new Parse.Query(Parse.User)
|
||||
.get(user.id, { useMasterKey: true })
|
||||
.then(fetchedUser => {
|
||||
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||
done();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get PII via REST', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.ssn).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST with self credentials', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST using master key', (done) => {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_User',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result.results[0];
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
expect(fetchedUser.ssn).toBe(SSN);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should not get PII via REST by ID', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test'
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(undefined);
|
||||
expect(fetchedUser.email).toBe(undefined);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with self credentials', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
|
||||
it('should get PII via REST by ID with master key', (done) => {
|
||||
request.get({
|
||||
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Javascript-Key': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
}
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
const fetchedUser = result;
|
||||
expect(fetchedUser.zip).toBe(ZIP);
|
||||
expect(fetchedUser.email).toBe(EMAIL);
|
||||
},
|
||||
e => console.error('error', e.message)
|
||||
).done(() => done());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,7 @@ export class Config {
|
||||
this.fileKey = cacheInfo.fileKey;
|
||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
||||
this.userSensitiveFields = cacheInfo.userSensitiveFields;
|
||||
|
||||
// Create a new DatabaseController per request
|
||||
if (cacheInfo.databaseController) {
|
||||
|
||||
@@ -113,6 +113,7 @@ class ParseServer {
|
||||
webhookKey,
|
||||
fileKey,
|
||||
facebookAppIds = [],
|
||||
userSensitiveFields = [],
|
||||
enableAnonymousUsers = defaults.enableAnonymousUsers,
|
||||
allowClientClassCreation = defaults.allowClientClassCreation,
|
||||
oauth = {},
|
||||
@@ -155,6 +156,11 @@ class ParseServer {
|
||||
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
|
||||
}
|
||||
|
||||
userSensitiveFields = Array.from(new Set(userSensitiveFields.concat(
|
||||
defaults.userSensitiveFields,
|
||||
userSensitiveFields
|
||||
)));
|
||||
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
|
||||
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
||||
logging.setLogger(loggerController);
|
||||
@@ -222,7 +228,8 @@ class ParseServer {
|
||||
revokeSessionOnPasswordReset,
|
||||
databaseController,
|
||||
schemaCacheTTL,
|
||||
enableSingleSchemaCache
|
||||
enableSingleSchemaCache,
|
||||
userSensitiveFields
|
||||
});
|
||||
|
||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatibility
|
||||
|
||||
@@ -386,6 +386,32 @@ RestQuery.prototype.replaceDontSelect = function() {
|
||||
})
|
||||
};
|
||||
|
||||
const cleanResultOfSensitiveUserInfo = function (result, auth, config) {
|
||||
delete result.password;
|
||||
|
||||
if (auth.isMaster || (auth.user && auth.user.id === result.objectId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const field of config.userSensitiveFields) {
|
||||
delete result[field];
|
||||
}
|
||||
};
|
||||
|
||||
const cleanResultAuthData = function (result) {
|
||||
if (result.authData) {
|
||||
Object.keys(result.authData).forEach((provider) => {
|
||||
if (result.authData[provider] === null) {
|
||||
delete result.authData[provider];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(result.authData).length == 0) {
|
||||
delete result.authData;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a promise for whether it was successful.
|
||||
// Populates this.response with an object that only has 'results'.
|
||||
RestQuery.prototype.runFind = function(options = {}) {
|
||||
@@ -406,18 +432,8 @@ RestQuery.prototype.runFind = function(options = {}) {
|
||||
this.className, this.restWhere, findOptions).then((results) => {
|
||||
if (this.className === '_User') {
|
||||
for (var result of results) {
|
||||
delete result.password;
|
||||
|
||||
if (result.authData) {
|
||||
Object.keys(result.authData).forEach((provider) => {
|
||||
if (result.authData[provider] === null) {
|
||||
delete result.authData[provider];
|
||||
}
|
||||
});
|
||||
if (Object.keys(result.authData).length == 0) {
|
||||
delete result.authData;
|
||||
}
|
||||
}
|
||||
cleanResultOfSensitiveUserInfo(result, this.auth, this.config);
|
||||
cleanResultAuthData(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,10 @@ export default {
|
||||
help: "Max file size for uploads.",
|
||||
default: "20mb"
|
||||
},
|
||||
"userSensitiveFields": {
|
||||
help: "Personally identifiable information fields in the user table the should be removed for non-authorized users.",
|
||||
default: "email"
|
||||
},
|
||||
"sessionLength": {
|
||||
env: "PARSE_SERVER_SESSION_LENGTH",
|
||||
help: "Session duration, defaults to 1 year",
|
||||
|
||||
@@ -31,5 +31,6 @@ export default {
|
||||
sessionLength: 31536000,
|
||||
expireInactiveSessions: true,
|
||||
revokeSessionOnPasswordReset: true,
|
||||
schemaCacheTTL: 5000 // in ms
|
||||
schemaCacheTTL: 5000, // in ms
|
||||
userSensitiveFields: ['email']
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user