Files
2026-07-23 02:20:59 -04:00

182 lines
7.6 KiB
Plaintext

<script>
function renderUsernameSuggestions(suggestions) {
const container = document.getElementById('usernameOptions');
const hidden = document.getElementById('selectedUid');
const selector = document.getElementById('usernameSelector');
container.innerHTML = '';
if (!suggestions || !suggestions.length) return;
selector.style.display = '';
suggestions.forEach(function(uid, i) {
const btn = document.createElement('button');
btn.type = 'button';
btn.dataset.uid = uid;
btn.textContent = uid;
btn.className = 'btn ' + (i === 0 ? 'btn-secondary' : 'btn-outline-secondary');
btn.onclick = function() {
container.querySelectorAll('.btn').forEach(function(b) {
b.className = 'btn btn-outline-secondary';
});
btn.className = 'btn btn-secondary';
hidden.value = uid;
const override = document.getElementById('adminUidOverride');
if (override) override.value = '';
};
container.appendChild(btn);
});
hidden.value = suggestions[0];
}
async function fetchUsernameSuggestions() {
const gnEl = document.querySelector('[name="givenName"]');
const snEl = document.querySelector('[name="sn"]');
const dobEl = document.querySelector('[name="dob"]');
if (!gnEl || !snEl) return;
const gn = gnEl.value.trim();
const sn = snEl.value.trim();
const dob = dobEl ? dobEl.value.trim() : '';
if (gn.length < 2 || sn.length < 2) return;
try {
const params = new URLSearchParams({ givenName: gn, sn, dob });
const resp = await fetch('/api/auth/username-suggestions?' + params);
const data = await resp.json();
renderUsernameSuggestions(data.suggestions);
} catch(e) {}
}
</script>
<% if (locals.adminMode) { %>
<script>
// A service account (a Unix-style account something runs as -- a media
// manager, a torrent client, ...) isn't a person: no birthday, nothing to
// agree to, and it gets one name (a username), not a first/last name.
// Toggling this swaps the person-shaped fields for a single account-name
// field instead of asking for throwaway values.
function toggleServiceAccountFields(el){
var checked = el.checked;
var $form = $(el).closest('form');
$form.find('[name=dob]').prop('required', !checked).closest('.mb-3').toggle(!checked);
$form.find('#tosAgree').prop('required', !checked).closest('.mb-3').toggle(!checked);
$form.find('#personNameFields').toggle(!checked);
$form.find('#serviceAccountNameField').toggle(checked);
// Service accounts aren't a person with a mailbox, and a blank
// password is fine (no userPassword attribute set -- the account
// simply can't bind). Disabling (not just hiding) keeps disabled
// fields out of both form serialization and validation.
$form.find('[name=mail]').prop('disabled', checked).closest('.mb-3').toggle(!checked);
$form.find('[name=userPassword]').prop('disabled', checked).closest('.mb-3').toggle(!checked);
$form.find('[name=passwordMatch]').prop('disabled', checked).closest('.mb-3').toggle(!checked);
if(checked){
// Filler values so the LDAP schema (inetOrgPerson requires sn) is
// satisfied; not shown anywhere, the account name is what matters.
$form.find('[name=givenName]').val('Service');
$form.find('[name=sn]').val('Account');
document.getElementById('selectedUid').value = '';
}else{
$form.find('[name=givenName]').val('');
$form.find('[name=sn]').val('');
document.getElementById('usernameSelector').style.display = 'none';
document.getElementById('serviceAccountName').value = '';
}
}
</script>
<% } %>
<form action="user/" method="post" onsubmit="formAJAX(this)">
<input type="hidden" class="form-control" name="delete" value="false" />
<% if (locals.adminMode) { %>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="isServiceAccount" name="isServiceAccount" onchange="toggleServiceAccountFields(this)">
<label class="form-check-label" for="isServiceAccount">
This is a service account <small class="text-muted">(not a person — e.g. a Unix account an app/service runs as)</small>
</label>
</div>
<div class="mb-3" id="serviceAccountNameField" style="display:none">
<label class="form-label">Account name</label>
<input type="text" class="form-control shadow" id="serviceAccountName" placeholder="stuff_manager"
oninput="document.getElementById('selectedUid').value = this.value.trim()" />
</div>
<% } %>
<div id="personNameFields">
<div class="mb-3">
<label class="form-label">First name</label>
<input type="text" class="form-control shadow" name="givenName" placeholder="John" validate=":3" onblur="fetchUsernameSuggestions()" />
<div class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label class="form-label">Last name</label>
<input type="text" class="form-control shadow" name="sn" placeholder="smith" validate=":3" onblur="fetchUsernameSuggestions()" />
</div>
</div>
<div class="mb-3">
<label class="form-label">Date of Birth</label>
<input type="date" class="form-control shadow" name="dob" required onchange="fetchUsernameSuggestions()" />
</div>
<div class="mb-3" id="usernameSelector" style="display:none">
<label class="form-label">Username</label>
<div id="usernameOptions" class="d-flex flex-wrap gap-2 mb-2"></div>
<input type="hidden" name="uid" id="selectedUid" />
<% if (locals.adminMode) { %>
<input type="text" class="form-control shadow mt-2" id="adminUidOverride"
placeholder="Custom username (admin only)"
oninput="document.getElementById('selectedUid').value = this.value.trim() || (document.querySelector('#usernameOptions .btn-secondary') && document.querySelector('#usernameOptions .btn-secondary').dataset.uid) || ''" />
<% } %>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="text" class="form-control shadow" name="mail" placeholder="jsmith@gmail.com" validate="email:3" />
</div>
<div class="mb-3">
<label class="form-label">SSH Public Key</label>
<input type="text" class="form-control shadow" name="sshPublicKey" placeholder="ssh-rsa AAAAB3NzaC1yc2EAAAADAQ..." />
</div>
<div class="mb-3">
<label class="form-label">Location (Site) <small class="text-muted">(optional)</small></label>
<input type="text" class="form-control shadow" name="location" placeholder="Site One" />
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" class="form-control shadow" name="userPassword" placeholder="Atleast 5 char. long" validate="password:5"/>
</div>
<div class="mb-3">
<label class="form-label">Again</label>
<input type="password" class="form-control shadow" name="passwordMatch" placeholder="Retype password" validate="eq:userPassword"/>
</div>
<div class="mb-3">
<label class="form-label">User Description (Optional)</label>
<textarea class="form-control shadow" name="description" placeholder="Admin group for gitea app"></textarea>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="tosAgree" name="tosAgree" required>
<label class="form-check-label" for="tosAgree">
I have read and agree to the <a href="/tos" target="_blank">Terms of Service</a>
</label>
</div>
<button type="submit" class="btn btn-outline-dark">Add</button>
</form>
<script>
$(document).ready(async function() {
const $loc = $('[name="location"]');
if (!$loc.val()) {
try {
const dirRes = await app.api.get('directory-admin/resources');
const resources = dirRes.results || [];
const site = resources.find(r => r.kind === 'site' && r.metadata && r.metadata.isCurrentSite);
if (site) {
$loc.val(site.name);
}
} catch(e) {}
}
});
</script>