Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/api_keys.cgi
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/api_keys.cgi

added on local at 2026-07-01 15:02:23

Added
+98
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 5c15823ce5a2
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# ContactForge - API Keys management.
4#======================================================================
5use strict;
6use warnings;
7
8use lib '/var/www/vhosts/3dshawn.com/crm.3dshawn.com';
9use CGI;
10use MODS::DBConnect;
11use MODS::Login;
12use MODS::ContactForge::Config;
13use MODS::ContactForge::Wrapper;
14use Digest::SHA qw(sha256_hex);
15
16my $q = CGI->new;
17my $form = $q->Vars;
18my $auth = MODS::Login->new;
19my $wrap = MODS::ContactForge::Wrapper->new;
20my $db = MODS::DBConnect->new;
21my $cfg = MODS::ContactForge::Config->new;
22my $DB = $cfg->settings('database_name');
23$|=1;
24my $userinfo = $auth->login_verify();
25if (!$userinfo) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
26my $uid = $userinfo->{user_id}; $uid =~ s/[^0-9]//g;
27
28my $dbh = $db->db_connect();
29my $flash = '';
30
31# Create
32if (($ENV{REQUEST_METHOD} || '') eq 'POST' && ($form->{op} || '') eq 'create') {
33 my $name = $form->{name} || 'Unnamed key'; $name =~ s/'/''/g; $name = substr($name, 0, 120);
34 # Generate token: cf_ + 40 hex chars
35 my @c = (0..9, 'a'..'f');
36 my $rand = ''; $rand .= $c[int(rand(scalar @c))] for 1..40;
37 my $token = "cf_$rand";
38 my $hash = sha256_hex($token);
39 my $prefix = substr($token, 0, 12);
40 $db->db_readwrite($dbh, qq~
41 INSERT INTO `${DB}`.api_keys
42 SET user_id='$uid', name='$name', token_hash='$hash',
43 token_prefix='$prefix', created_at=NOW()
44 ~, $ENV{SCRIPT_NAME}, __LINE__);
45 $flash = qq~Your new API key (copy now -- you won't see it again):<br><code style="background:#0b0f1a;padding:6px 10px;border-radius:4px;color:#67e8f9;font-size:13px;display:inline-block;margin-top:6px">$token</code>~;
46}
47
48# Revoke
49if (($ENV{REQUEST_METHOD} || '') eq 'POST' && ($form->{op} || '') eq 'revoke') {
50 my $kid = $form->{id} || 0; $kid =~ s/[^0-9]//g;
51 if ($kid) {
52 $db->db_readwrite($dbh, qq~
53 UPDATE `${DB}`.api_keys SET revoked_at=NOW()
54 WHERE id='$kid' AND user_id='$uid'
55 ~, $ENV{SCRIPT_NAME}, __LINE__);
56 $flash = 'Key revoked.';
57 }
58}
59
60my @keys = $db->db_readwrite_multiple($dbh, qq~
61 SELECT id, name, token_prefix, last_used_at, revoked_at, created_at
62 FROM `${DB}`.api_keys
63 WHERE user_id='$uid'
64 ORDER BY id DESC
65~, $ENV{SCRIPT_NAME}, __LINE__);
66
67$db->db_disconnect($dbh);
68
69my $body = qq~
70<div class="page-header" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:18px">
71 <div>
72 <h1 style="margin:0;font-size:28px">API Keys</h1>
73 <p class="text-secondary" style="margin:6px 0 0;font-size:14px">Programmatic access for scripts and integrations. Treat each token like a password.</p>
74 </div>
75 <form method="POST" style="display:flex;gap:8px">
76 <input type="hidden" name="op" value="create">
77 <input type="text" name="name" placeholder="Key name (e.g. 'Zapier production')" required style="padding:8px 12px;border:1px solid var(--col-border);border-radius:8px;background:var(--col-surface-2);color:var(--col-text);font-size:14px;width:240px">
78 <button type="submit" class="btn btn-primary" style="white-space:nowrap">+ New key</button>
79 </form>
80</div>
81~;
82$body .= qq~<div class="card" style="padding:16px;margin-bottom:18px;background:rgba(6,182,212,0.10);border:1px solid rgba(6,182,212,0.35);border-radius:10px;color:#67e8f9">$flash</div>~ if $flash;
83
84if (!@keys) {
85 $body .= qq~<div class="card" style="padding:32px;text-align:center;color:var(--col-text-2)">No keys yet. Create one above to start using the REST API. See <a href="/api_docs.cgi" style="color:#67e8f9">API docs</a> for endpoints.</div>~;
86} else {
87 $body .= q~<div class="card" style="padding:0;overflow:hidden"><table class="table" style="width:100%"><thead><tr><th style="text-align:left;padding:12px 16px">Name</th><th style="text-align:left;padding:12px 16px">Prefix</th><th style="text-align:left;padding:12px 16px">Last used</th><th style="text-align:left;padding:12px 16px">Created</th><th style="text-align:right;padding:12px 16px">Action</th></tr></thead><tbody>~;
88 foreach my $k (@keys) {
89 my $status = $k->{revoked_at} ? '<span style="color:#ef4444">revoked</span>' : '<span style="color:#10b981">active</span>';
90 my $last = $k->{last_used_at} || 'never';
91 my $action = $k->{revoked_at} ? '&mdash;' : qq~<form method="POST" style="display:inline" onsubmit="return confirm('Revoke this key? Any script using it will stop working.')"><input type="hidden" name="op" value="revoke"><input type="hidden" name="id" value="$k->{id}"><button type="submit" class="btn btn-secondary btn-sm" style="background:transparent;border:1px solid #ef4444;color:#ef4444">Revoke</button></form>~;
92 $body .= qq~<tr><td style="padding:12px 16px">$k->{name} &middot; $status</td><td style="padding:12px 16px;font-family:monospace;font-size:13px">$k->{token_prefix}&hellip;</td><td style="padding:12px 16px">$last</td><td style="padding:12px 16px">$k->{created_at}</td><td style="padding:12px 16px;text-align:right">$action</td></tr>~;
93 }
94 $body .= '</tbody></table></div>';
95}
96
97print qq~Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n~;
98$wrap->render({userinfo=>$userinfo, page_key=>'api_keys', title=>'API Keys', body=>$body});
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help