MRBS 1.12.2 等保2.0二级整改完整提交
Docker image / push (push) Canceled after 0s

包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、登录页JS修复等全部改动
This commit is contained in:
人事系统开发
2026-09-08 21:19:47 +08:00
commit 48092cab42
2221 changed files with 659586 additions and 0 deletions
+59
View File
@@ -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";
+237
View File
@@ -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);
})
}