makes max IP limit configurable per-user
This commit is contained in:
@@ -165,8 +165,8 @@ router.post("/reactivate-user/:token", (req, res) => {
|
||||
|
||||
userStore.upsertUser({
|
||||
token: user.token,
|
||||
disabledAt: 0,
|
||||
disabledReason: "",
|
||||
disabledAt: null,
|
||||
disabledReason: null,
|
||||
});
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
@@ -80,41 +80,8 @@
|
||||
const state = localStorage.getItem("showNicknames") === "true";
|
||||
document.getElementById("toggle-nicknames").checked = state;
|
||||
toggleNicknames();
|
||||
|
||||
document.querySelectorAll("td.actions a.ban").forEach(function (a) {
|
||||
a.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var token = a.getAttribute("data-token");
|
||||
if (confirm("Are you sure you want to ban this user?")) {
|
||||
let reason = prompt("Reason for ban:");
|
||||
fetch(
|
||||
"/admin/manage/disable-user/" + token,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ reason, _csrf: document.querySelector("meta[name=csrf-token]").getAttribute("content") }),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
}).then(() => window.location.reload());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("td.actions a.unban").forEach(function (a) {
|
||||
a.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var token = a.getAttribute("data-token");
|
||||
if (confirm("Are you sure you want to unban this user?")) {
|
||||
fetch(
|
||||
"/admin/manage/reactivate-user/" + token,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ _csrf: document.querySelector("meta[name=csrf-token]").getAttribute("content") }),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
}
|
||||
).then(() => window.location.reload());
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<%- include("partials/admin-ban-xhr-script") %>
|
||||
|
||||
<%- include("partials/admin-footer") %>
|
||||
|
||||
@@ -41,17 +41,35 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Disabled At</th>
|
||||
<td colspan="2"><%- user.disabledAt %></td>
|
||||
<td><%- user.disabledAt %></td>
|
||||
<td class="actions">
|
||||
<% if (user.disabledAt) { %>
|
||||
<a title="Unban" href="#" class="unban" data-token="<%= user.token %>">🔄️</a>
|
||||
<% } else { %>
|
||||
<a title="Ban" href="#" class="ban" data-token="<%= user.token %>">🚫</a>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Disabled Reason</th>
|
||||
<td colspan="2"><%- user.disabledReason %></td>
|
||||
<td><%- user.disabledReason %></td>
|
||||
<% if (user.disabledAt) { %>
|
||||
<td class="actions">
|
||||
<a title="Edit" id="edit-disabledReason" href="#" data-field="disabledReason"
|
||||
data-token="<%= user.token %>">✏️</a>
|
||||
</td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP Address Limit</th>
|
||||
<td><%- (user.maxIps ?? maxIps) || "Unlimited" %></td>
|
||||
<td class="actions">
|
||||
<a title="Edit" id="edit-maxIps" href="#" data-field="maxIps" data-token="<%= user.token %>">✏️</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IPs</th>
|
||||
<td colspan="2">
|
||||
<%- include("partials/shared_user_ip_list", { user, shouldRedact: false }) %>
|
||||
</td>
|
||||
<td colspan="2"><%- include("partials/shared_user_ip_list", { user, shouldRedact: false }) %></td>
|
||||
</tr>
|
||||
<% if (user.type === "temporary") { %>
|
||||
<tr>
|
||||
@@ -69,9 +87,7 @@
|
||||
<input type="hidden" name="_csrf" value="<%- csrfToken %>" />
|
||||
<button type="submit" class="btn btn-primary">Refresh Quotas for User</button>
|
||||
</form>
|
||||
<% } %>
|
||||
<%- include("partials/shared_quota-info", { quota, user }) %>
|
||||
|
||||
<% } %> <%- include("partials/shared_quota-info", { quota, user }) %>
|
||||
|
||||
<p><a href="/admin/manage/list-users">Back to User List</a></p>
|
||||
|
||||
@@ -112,4 +128,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<%- include("partials/admin-footer") %>
|
||||
<%- include("partials/admin-ban-xhr-script") %> <%- include("partials/admin-footer") %>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script>
|
||||
document.querySelectorAll("td.actions a.ban").forEach(function (a) {
|
||||
a.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var token = a.getAttribute("data-token");
|
||||
if (confirm("Are you sure you want to ban this user?")) {
|
||||
let reason = prompt("Reason for ban:");
|
||||
fetch("/admin/manage/disable-user/" + token, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ reason, _csrf: document.querySelector("meta[name=csrf-token]").getAttribute("content") }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}).then(() => window.location.reload());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("td.actions a.unban").forEach(function (a) {
|
||||
a.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var token = a.getAttribute("data-token");
|
||||
if (confirm("Are you sure you want to unban this user?")) {
|
||||
fetch("/admin/manage/reactivate-user/" + token, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ _csrf: document.querySelector("meta[name=csrf-token]").getAttribute("content") }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}).then(() => window.location.reload());
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -50,6 +50,8 @@ export const UserSchema = z
|
||||
disabledReason: z.string().optional(),
|
||||
/** Time at which the user will expire and be disabled (for temp users). */
|
||||
expiresAt: z.number().optional(),
|
||||
/** The user's maximum number of IP addresses; supercedes global max. */
|
||||
maxIps: z.coerce.number().int().min(0).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ import { User, UserUpdate } from "./schema";
|
||||
|
||||
const log = logger.child({ module: "users" });
|
||||
|
||||
const MAX_IPS_PER_USER = config.maxIpsPerUser;
|
||||
|
||||
const users: Map<string, User> = new Map();
|
||||
const usersToFlush = new Set<string>();
|
||||
let quotaRefreshJob: schedule.Job | null = null;
|
||||
@@ -173,10 +171,10 @@ export function authenticate(token: string, ip: string) {
|
||||
const user = users.get(token);
|
||||
if (!user || user.disabledAt) return;
|
||||
if (!user.ip.includes(ip)) user.ip.push(ip);
|
||||
|
||||
// If too many IPs are associated with the user, disable the account.
|
||||
|
||||
const configIpLimit = user.maxIps ?? config.maxIpsPerUser;
|
||||
const ipLimit =
|
||||
user.type === "special" || !MAX_IPS_PER_USER ? Infinity : MAX_IPS_PER_USER;
|
||||
user.type === "special" || !configIpLimit ? Infinity : configIpLimit;
|
||||
if (user.ip.length > ipLimit) {
|
||||
disableUser(token, "IP address limit exceeded.");
|
||||
return;
|
||||
|
||||
@@ -20,7 +20,12 @@ router.get("/", (_req, res) => {
|
||||
});
|
||||
|
||||
router.get("/lookup", (_req, res) => {
|
||||
res.render("user_lookup", { user: res.locals.currentSelfServiceUser });
|
||||
const ipLimit =
|
||||
(res.locals.currentSelfServiceUser?.maxIps ?? config.maxIpsPerUser) || 0;
|
||||
res.render("user_lookup", {
|
||||
user: res.locals.currentSelfServiceUser,
|
||||
ipLimit,
|
||||
});
|
||||
});
|
||||
|
||||
router.post("/lookup", (req, res) => {
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<td colspan="2"><%- user.lastUsedAt || "never" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IPs<%- maxIps ? ` (max ${maxIps})` : "" %> </th>
|
||||
<th scope="row">IPs<%- ipLimit ? ` (max ${ipLimit})` : "" %></th>
|
||||
<td colspan="2"><%- include("partials/shared_user_ip_list", { user, shouldRedact: true }) %></td>
|
||||
</tr>
|
||||
<% if (user.type === "temporary") { %>
|
||||
|
||||
Reference in New Issue
Block a user