包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、登录页JS修复等全部改动
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
MRBS provides an authentication type that allows the authentication of users
|
||||
against Wix websites. To install and configure this, you need to make changes
|
||||
on both the Wix and MRBS servers.
|
||||
|
||||
On the Wix site
|
||||
===============
|
||||
|
||||
1. Create a http-functions.js file in your Wix backend (see the Wix YouTube
|
||||
video at https://www.youtube.com/watch?v=4yCBplV3MPQ for how to do it) and copy
|
||||
and paste the contents of the file wix/web/http-functions.js into it. You will
|
||||
need to open the file with a suitable text editor such as Notepad++. If you
|
||||
already have a http-functions.js file you will need to merge the contents with
|
||||
your existing file.
|
||||
|
||||
2. Publish the file by clicking the "Publish" button.
|
||||
|
||||
3. In the Wix Secrets Manager (found under Developer Tools in your Wix
|
||||
dashboard) create a new secret called "MRBS_API_key". (You can choose your own
|
||||
name, but if so you will need to change the MRBS configuration - see below.)
|
||||
Generate an API key (search the web for "API key generator" or "GUID generator".
|
||||
Copy and paste this key into the secret and store it.
|
||||
|
||||
4. In your Wix dashboard, go to Contacts then Site Members. Under "Manage
|
||||
Badges" create a new badge called "MRBS Admin" and assign this badge to those
|
||||
members that should be admins. You can call the badge something else, in which
|
||||
case you should change the relevant MRBS config setting - see below.
|
||||
|
||||
[Using badges is not ideal for this purpose. The best solution would be to use
|
||||
Wix roles, but as of Jan 2022 there doesn't seem to be a way of getting the
|
||||
roles of a member given a memberId. Custom fields in members could be another
|
||||
solution, but there doesn't seem to be a way of stopping a member from editing
|
||||
their own custom field value in their profile. Therefore badges seem to be the
|
||||
only solution. Note though that badge titles don't have to be unique, so care
|
||||
should be taken. It might be better to use badge ids, but there doesn't seem
|
||||
to be a way for a Wix administrator to discover the id of a badge from the
|
||||
dashboard.]
|
||||
|
||||
|
||||
On the MRBS site
|
||||
================
|
||||
|
||||
Set the following configuration variables in your config.inc.php file:
|
||||
|
||||
$auth['type'] = "wix";
|
||||
|
||||
// The URL of your WIX site
|
||||
$auth['wix']['site_url'] = "https://example.com/";
|
||||
|
||||
// The API key that you generated and saved in your Wix secrets manager.
|
||||
$auth['wix']['mrbs_api_key'] = "";
|
||||
|
||||
// The name of the secret in your Wix secrets manager
|
||||
$auth['wix']['mrbs_api_key_secret_name'] = "MRBS_API_key";
|
||||
|
||||
// The name (title) of the badge that determines whether a member is an
|
||||
// MRBS admin. Note that badge names are case-sensitive. You can also
|
||||
// configure admins in the config file by using
|
||||
// $auth['admin'][] = "someone@example.com";
|
||||
$auth['wix']['admin_badge'] = "MRBS Admin";
|
||||
@@ -0,0 +1,237 @@
|
||||
import {forbidden, ok, serverError} from 'wix-http-functions';
|
||||
import {authentication} from 'wix-members-backend';
|
||||
import wixData from 'wix-data';
|
||||
import {contacts} from 'wix-crm-backend';
|
||||
import wixSecretsBackend from 'wix-secrets-backend';
|
||||
|
||||
|
||||
// Validates that a request is valid, ie that the requesting server has used
|
||||
// a valid API key, ie one that matches the one held in the Wix secrets manager.
|
||||
// Parameters:
|
||||
// request the request
|
||||
// data the data in the request which must include
|
||||
// key the API key
|
||||
// secret_name the name of the secret in the Wix secrets manager that holds the API key
|
||||
function validateRequest(request, data) {
|
||||
|
||||
return wixSecretsBackend.getSecret(data.secret_name)
|
||||
.then((secret) => {
|
||||
if (secret === data.key) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
console.log("MRBS: invalid API key passed by IP address " + request.headers['x-real-ip']);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// The exported functions work by firing off two promises in parallel: the first
|
||||
// validates that the request comes from an authorised server and the second does
|
||||
// the actual work. When the two promises have been resolved or rejected, this
|
||||
// function processes the promise results and issues the appropriate response.
|
||||
function processPromiseResults(promiseResults) {
|
||||
|
||||
let result = {
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
if ((promiseResults[0].status === 'rejected') || (promiseResults[1].status === 'rejected')) {
|
||||
result.body = "internal server error";
|
||||
return serverError(result);
|
||||
}
|
||||
else if (promiseResults[0].value === false) {
|
||||
result.body = "forbidden";
|
||||
return forbidden(result);
|
||||
}
|
||||
else {
|
||||
result.body = promiseResults[1].value;
|
||||
return ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Validates a member's email login and password. Returns a boolean.
|
||||
// Request data parameters:
|
||||
// email the member's login email address
|
||||
// password the password
|
||||
export async function post_validateMember(request) {
|
||||
|
||||
const data = await request.body.json();
|
||||
|
||||
const validateRequestPromise = validateRequest(request, data);
|
||||
|
||||
const validateMemberPromise = authentication.login(data.email, data.password)
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
// If the email address and password are not valid then we will get
|
||||
// an UNAUTHORIZED error. If it's any other kind then log it.
|
||||
if (error.details.applicationError.code === "UNAUTHORIZED") {
|
||||
console.error(error);
|
||||
}
|
||||
// Return false whatever the error
|
||||
return false;
|
||||
});
|
||||
|
||||
return Promise.allSettled([validateRequestPromise, validateMemberPromise])
|
||||
.then((promiseResults) => {
|
||||
return processPromiseResults(promiseResults);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Gets a member's details given an email address. Returns a JSON object or NULL.
|
||||
// Request data parameters:
|
||||
// email the member's login email address
|
||||
export async function post_getMemberByEmail(request) {
|
||||
|
||||
const data = await request.body.json();
|
||||
|
||||
const options = {
|
||||
"suppressAuth": true,
|
||||
"suppressHooks": true
|
||||
};
|
||||
|
||||
const validateRequestPromise = validateRequest(request, data);
|
||||
|
||||
const getMemberPromise = wixData.query("Members/PrivateMembersData")
|
||||
.eq("loginEmail", data.email)
|
||||
.limit(1)
|
||||
.find(options)
|
||||
.then((members) => {
|
||||
if(members.items.length > 0) {
|
||||
let member = members.items[0];
|
||||
// Now we've got the member we have to get (a) their full details (including
|
||||
// custom fields, which aren't in PrivateMembersData) from Contacts using
|
||||
// the id and (b) their badges from Members/Badges. Get these two sets of
|
||||
// data in parallel using promises.
|
||||
const getContactPromise = contacts.getContact(member._id, {suppressAuth: true})
|
||||
.then((contact) => {
|
||||
return {
|
||||
member: member,
|
||||
contact: contact
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
const getBadgesPromise = wixData.query("Members/Badges")
|
||||
.find()
|
||||
.then((results) => {
|
||||
return results.items;
|
||||
} );
|
||||
|
||||
return Promise.allSettled([getContactPromise, getBadgesPromise])
|
||||
.then((promiseResults) => {
|
||||
if ((promiseResults[0].status === 'fulfilled') && (promiseResults[1].status ==='fulfilled')) {
|
||||
let result = promiseResults[0].value;
|
||||
result.badges = [];
|
||||
// Iterate through the badges checking if this member has the badge
|
||||
if (promiseResults[1].value) {
|
||||
promiseResults[1].value.forEach(badge => {
|
||||
if (badge.members.includes(member._id)) {
|
||||
result.badges.push(badge.title);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return Promise.allSettled([validateRequestPromise, getMemberPromise])
|
||||
.then((promiseResults) => {
|
||||
return processPromiseResults(promiseResults);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Returns an array of members indexed by 'username' and 'display_name'
|
||||
// Request data parameters:
|
||||
// limit (optional) the limit to be used in each query. Defaults to 50.
|
||||
// display_name_property (optional) the member property to be used for the display name.
|
||||
// Typically either 'name' (the default) or 'nickname'.
|
||||
export async function post_getMemberNames(request) {
|
||||
|
||||
const data = await request.body.json();
|
||||
|
||||
const displayNameProperty = data.display_name_property ?? 'name';
|
||||
|
||||
const options = {
|
||||
"suppressAuth": true,
|
||||
"suppressHooks": true
|
||||
};
|
||||
|
||||
const defaultLimit = 50;
|
||||
let memberNames = [];
|
||||
let limit = defaultLimit;
|
||||
|
||||
if (data.limit !== undefined) {
|
||||
limit = parseInt(data.limit, 10);
|
||||
if (isNaN(limit) || (limit <= 0)) {
|
||||
limit =defaultLimit;
|
||||
}
|
||||
}
|
||||
|
||||
function extractMemberNames(items) {
|
||||
|
||||
let result = [];
|
||||
|
||||
items.forEach(function(item) {
|
||||
result.push({
|
||||
username: item.loginEmail,
|
||||
display_name: ((item[displayNameProperty] === undefined) ||
|
||||
(item[displayNameProperty] === null) ||
|
||||
(item[displayNameProperty] === '')) ? item.loginEmail : item[displayNameProperty]
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const validateRequestPromise = validateRequest(request, data);
|
||||
|
||||
const getMemberNamesPromise = wixData.query("Members/PrivateMembersData")
|
||||
.limit(limit)
|
||||
.find(options)
|
||||
.then(async (results) => {
|
||||
memberNames = memberNames.concat(extractMemberNames(results.items));
|
||||
while (results.hasNext()) {
|
||||
results = await results.next();
|
||||
memberNames = memberNames.concat(extractMemberNames(results.items));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
})
|
||||
.then(() => {
|
||||
return memberNames;
|
||||
})
|
||||
|
||||
return Promise.allSettled([validateRequestPromise, getMemberNamesPromise])
|
||||
.then((promiseResults) => {
|
||||
return processPromiseResults(promiseResults);
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user