// API keys on the account page: list, make (with a permission picker of the maker's own permissions), rotate, revoke (function () { var card = document.getElementById('apiKeys'); if (!card) return; var accountId = card.dataset.account; var isSelf = card.dataset.self === '1'; var canCreate = card.dataset.canCreate === '1'; var $ = function (id) { return document.getElementById(id); }; var catalog = null; var titles = {}; var STATUS = { active: ['Active', 'success'], revoked: ['Revoked', 'secondary'], expired: ['Expired', 'secondary'], signed_out: ['Stopped by sign out', 'warning'] }; function loadCatalog() { if (catalog) return Promise.resolve(catalog); return api.get('/api/api_keys/permissions').then(function (d) { if (!d.success) throw new Error(d.error || 'Could not load permissions'); catalog = d; d.permissions.forEach(function (p) { titles[p.key] = p.title; }); return d; }); } function scopeCell(k) { var html = k.allPermissions ? fmt.badge('All of ' + (isSelf ? 'your' : 'their') + ' permissions', 'primary') : '' + esc(k.permissions.length) + ' permission' + (k.permissions.length === 1 ? '' : 's') + ''; if (k.readOnly) html += ' ' + fmt.badge('read-only', 'info'); if (k.lostPermissions.length) { html += '
Not working: ' + esc(k.lostPermissions.join(', ')) + '
'; } if (k.allowedIps) html += '
From ' + esc(k.allowedIps) + '
'; if (k.allowedPaths) html += '
Paths ' + esc(k.allowedPaths) + '
'; return html; } function limitsCell(k) { var html = esc(k.effectiveRateLimit) + '/min' + (k.rateLimit ? '' : ' (default)'); if (k.dailyQuota) html += '
' + esc(k.todayCount) + ' of ' + esc(k.dailyQuota) + ' today
'; return html; } function row(k) { var status = STATUS[k.status] || [k.status, 'secondary']; var active = k.status === 'active' || k.status === 'signed_out'; var actions = ''; if (isSelf && k.status !== 'revoked' && k.status !== 'expired' && canCreate) actions += ''; if (k.status !== 'revoked') actions += ''; return '' + '' + esc(k.name) + ' ' + esc(k.prefix) + '…' + (k.note ? '
' + esc(k.note) + '
' : '') + '
Made ' + fmt.unix(k.createdAt) + (k.createdBy ? ' by ' + esc(k.createdBy) : '') + '
' + '' + scopeCell(k) + '' + '' + limitsCell(k) + '' + '' + (k.lastUsedAt ? fmt.unix(k.lastUsedAt) + '
' + esc(k.lastIp) + '
' : 'Never') + '' + '' + esc(k.requestCount) + '' + '' + (k.expiresAt ? fmt.unix(k.expiresAt) : 'Never') + '' + '' + fmt.badge(status[0], status[1]) + (k.revokedAt ? '
' + fmt.unix(k.revokedAt) + (k.revokedBy ? ' by ' + esc(k.revokedBy) : '') + '
' : '') + '' + '' + actions + ''; } function load() { return loadCatalog().catch(function () { return null; }).then(function () { return api.get('/api/accounts/' + accountId + '/api_keys'); }).then(function (d) { var rows = $('apiKeyRows'); if (!d.success) { rows.innerHTML = '' + esc(d.error || 'Could not load the keys') + ''; return; } rows.innerHTML = d.keys.length ? d.keys.map(row).join('') : 'No API keys.'; }); } // The maker's own permissions, grouped by category like the Permissions page function renderPicker() { var groups = {}; var order = []; catalog.permissions.forEach(function (p) { if (!p.allowed) return; if (!groups[p.category]) { groups[p.category] = []; order.push(p.category); } groups[p.category].push(p); }); $('apiKeyPicker').innerHTML = order.map(function (category, i) { return '
' + esc(category) + '
' + groups[category].map(function (p) { var id = 'apiKeyPerm_' + p.key; return '
' + '' + '
'; }).join('') + '
'; }).join('') || '

You have no permissions to give a key.

'; $('apiKeyRate').placeholder = 'Default (' + catalog.defaultRateLimit + ')'; $('apiKeyRate').max = catalog.maxRateLimit; } function showForm(show) { $('apiKeyForm').classList.toggle('d-none', !show); if (show) loadCatalog().then(renderPicker).catch(function (e) { toast(e.message, 'danger'); }); } function showSecret(key) { $('apiKeySecret').value = key; $('apiKeyCreated').classList.remove('d-none'); $('apiKeySecret').select(); } if ($('apiKeyNew')) $('apiKeyNew').addEventListener('click', function () { showForm(true); }); $('apiKeyCancel').addEventListener('click', function () { showForm(false); }); $('apiKeyCopy').addEventListener('click', function () { var box = $('apiKeySecret'); box.select(); if (navigator.clipboard) navigator.clipboard.writeText(box.value).then(function () { toast('Copied', 'success'); }); }); $('apiKeyScopeAll').addEventListener('change', function () { $('apiKeyPicker').classList.add('d-none'); }); $('apiKeyScopeSome').addEventListener('change', function () { $('apiKeyPicker').classList.remove('d-none'); }); $('apiKeyPicker').addEventListener('click', function (e) { var group = e.target.dataset.group; if (group === undefined) return; var boxes = $('apiKeyPicker').querySelectorAll('[data-group-of="' + group + '"]'); var check = Array.prototype.some.call(boxes, function (b) { return !b.checked; }); boxes.forEach(function (b) { b.checked = check; }); }); $('apiKeyForm').addEventListener('submit', function (e) { e.preventDefault(); var all = $('apiKeyScopeAll').checked; var picked = Array.prototype.map.call($('apiKeyPicker').querySelectorAll('input:checked'), function (b) { return b.value; }); if (!all && !picked.length) { toast('Pick at least one permission, or all of yours', 'warning'); return; } api.action('/api/api_keys', { name: $('apiKeyName').value, note: $('apiKeyNote').value, permissions: all ? '*' : picked, readOnly: $('apiKeyReadOnly').checked, allowedIps: $('apiKeyIps').value, allowedPaths: $('apiKeyPaths').value, rateLimit: parseInt($('apiKeyRate').value, 10) || 0, dailyQuota: parseInt($('apiKeyQuota').value, 10) || 0, expiresInDays: parseInt($('apiKeyExpiry').value, 10) || 0 }).then(function (d) { $('apiKeyForm').reset(); $('apiKeyPicker').classList.remove('d-none'); showForm(false); showSecret(d.key); load(); }).catch(function () {}); }); $('apiKeyRows').addEventListener('click', function (e) { var rotate = e.target.dataset.rotate, revoke = e.target.dataset.revoke; if (rotate) { if (!confirm('Make a new secret for this key? The current one stops working at once.')) return; api.action('/api/api_keys/' + rotate + '/rotate', {}).then(function (d) { showSecret(d.key); load(); }).catch(function () {}); } else if (revoke) { if (!confirm('Revoke this key? Anything using it stops working at once.')) return; api.action('/api/api_keys/' + revoke + '/revoke', {}, 'API key revoked').then(load).catch(function () {}); } }); load(); })();