Skip to content

Commit 996fa07

Browse files
h0tak88rclaude
andcommitted
feat(settings): unify platform credentials into one DB-backed accounts manager
Merge the two overlapping Settings sections (Multiple Accounts + the single-key Bug Bounty Platform API Keys) into one 'Bug Bounty Platform Accounts' manager that is the single source of truth — all in the DB, with per-account validity tags. - accounts.MigrateEnvAccounts(): one-shot, idempotent import of the legacy single-account env creds (H1_USERNAME/H1_TOKEN, BUGCROWD_TOKEN, INTIGRITI_TOKEN, YWH_*) into bbp_accounts as a 'default' account per platform. Guarded by a settings marker; runs at boot after HydrateEnvFromDB. - accounts.For() stops injecting the env account once migrated, so the DB is authoritative (no lingering env credential after a user edits/deletes an account). - UI: remove the redundant HackerOne/Bugcrowd/Intigriti/YesWeHack single-key rows (now managed as accounts); the remaining single-value keys (HackAdvisor aggregator, Chaos recon) move to a small 'External Aggregators & Recon Keys' section. Accounts header/description updated; Bugcrowd cookie note retained. - test: env→DB import, no env duplicate in For(), idempotency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4fb080d commit 996fa07

4 files changed

Lines changed: 119 additions & 28 deletions

File tree

internal/accounts/accounts.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,61 @@ func For(platform string) []Account {
110110
}
111111
}
112112

113-
// Add the legacy env account unless its token is already represented.
114-
if env := envAccount(p); hasCreds(env) && (env.Token == "" || !seenTok[env.Token]) {
115-
out = append(out, env)
113+
// Add the legacy env account unless its token is already represented — but only
114+
// until the env credentials have been migrated into the DB (see
115+
// MigrateEnvAccounts). After migration the DB is the single source of truth, so
116+
// the env vars are no longer injected as an implicit extra account.
117+
if !envMigrated() {
118+
if env := envAccount(p); hasCreds(env) && (env.Token == "" || !seenTok[env.Token]) {
119+
out = append(out, env)
120+
}
116121
}
117122
return out
118123
}
119124

125+
const envMigratedMarker = "bbp_env_migrated"
126+
127+
// envMigrated reports whether the one-time env→DB account migration has run.
128+
func envMigrated() bool {
129+
v, _ := db.GetSetting(envMigratedMarker)
130+
return v == "done"
131+
}
132+
133+
// MigrateEnvAccounts imports the legacy single-account env credentials
134+
// (H1_USERNAME/H1_TOKEN, BUGCROWD_TOKEN, INTIGRITI_TOKEN, YWH_*) into the
135+
// bbp_accounts DB table as a "default" account per platform, so the DB becomes
136+
// the single source of truth for the Settings accounts manager. It is idempotent
137+
// and one-shot (guarded by a settings marker), so it is safe to call on every
138+
// boot. An env credential already present as a DB account (same token) is skipped.
139+
func MigrateEnvAccounts() {
140+
if envMigrated() {
141+
return
142+
}
143+
for _, p := range []string{"h1", "bc", "it", "ywh"} {
144+
env := envAccount(p)
145+
if !hasCreds(env) {
146+
continue
147+
}
148+
exists := false
149+
if rows, err := db.ListBBPAccounts(p); err == nil {
150+
for _, r := range rows {
151+
if r.Token != "" && r.Token == env.Token {
152+
exists = true
153+
break
154+
}
155+
}
156+
}
157+
if exists {
158+
continue
159+
}
160+
_, _ = db.UpsertBBPAccount(db.BBPAccount{
161+
Platform: p, Label: "default", Username: env.Username,
162+
Token: env.Token, Email: env.Email, Password: env.Password, Enabled: true,
163+
})
164+
}
165+
_ = db.SetSetting(envMigratedMarker, "done")
166+
}
167+
120168
// Labels returns the account labels for a platform (for source tagging).
121169
func Labels(platform string) []string {
122170
var ls []string

internal/accounts/accounts_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package accounts
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/h0tak88r/AutoAR/internal/db"
8+
)
9+
10+
// TestMigrateEnvAccounts verifies the one-shot env→DB import: legacy env
11+
// credentials become "default" DB accounts, For() then serves them from the DB
12+
// without also injecting the env account (no duplicate), and re-running is a no-op.
13+
func TestMigrateEnvAccounts(t *testing.T) {
14+
t.Setenv("DB_TYPE", "sqlite")
15+
t.Setenv("DB_HOST", filepath.Join(t.TempDir(), "acct_test.db"))
16+
t.Setenv("AUTOAR_SILENT", "true")
17+
if err := db.Init(); err != nil {
18+
t.Fatalf("db.Init: %v", err)
19+
}
20+
if err := db.EnsureSchema(); err != nil {
21+
t.Fatalf("db.EnsureSchema: %v", err)
22+
}
23+
24+
t.Setenv("H1_USERNAME", "0x88")
25+
t.Setenv("H1_TOKEN", "h1-secret")
26+
t.Setenv("BUGCROWD_TOKEN", "bc-secret")
27+
28+
MigrateEnvAccounts()
29+
30+
// h1 env creds imported as a "default" DB account.
31+
rows, err := db.ListBBPAccounts("h1")
32+
if err != nil {
33+
t.Fatalf("ListBBPAccounts(h1): %v", err)
34+
}
35+
if len(rows) != 1 || rows[0].Label != "default" || rows[0].Username != "0x88" || rows[0].Token != "h1-secret" {
36+
t.Fatalf("h1 not imported correctly: %+v", rows)
37+
}
38+
39+
// After migration, For(h1) serves exactly the DB account — the env account is
40+
// no longer injected (would otherwise duplicate the same credential).
41+
acc := For("h1")
42+
if len(acc) != 1 || acc[0].Source != "db" || acc[0].Token != "h1-secret" {
43+
t.Fatalf("For(h1) after migration = %+v, want one db account", acc)
44+
}
45+
46+
// Idempotent: a second run must not create duplicates.
47+
MigrateEnvAccounts()
48+
if rows2, _ := db.ListBBPAccounts("h1"); len(rows2) != 1 {
49+
t.Fatalf("migration not idempotent: %d h1 rows", len(rows2))
50+
}
51+
}

internal/api/ui/pages/settings.js

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,15 @@
144144
</div>
145145
146146
<div class="settings-section" data-tab="platforms">
147-
<div class="settings-section-header"> Multiple Accounts</div>
147+
<div class="settings-section-header"> Bug Bounty Platform Accounts</div>
148148
<div class="settings-section-description">
149-
Add more than one credential per platform to pull programs, domains and scope from
150-
<strong>all</strong> of your accounts at once. Every enabled account is queried and the
151-
results are merged (deduplicated). The single credential in the section below is used
152-
automatically as an extra <code>env</code> account.
149+
The single source for HackerOne, Bugcrowd, Intigriti and YesWeHack credentials — all
150+
stored in the database and surviving redeploys. Add one or more accounts per platform;
151+
every <strong>enabled</strong> account is queried and the programs, domains and scope
152+
are merged (deduplicated). Each account shows a live <strong>validity</strong> tag.
153+
Any credential you had in the old single-key fields was imported here automatically.
154+
<br><em>Bugcrowd</em> takes the <code>_crowdcontrol_session_key</code> cookie value from
155+
your logged-in browser (DevTools → Cookies) — not the "API Credentials" token.
153156
</div>
154157
<div class="settings-section-body">
155158
<div id="settings-accounts-manager" style="padding:8px 24px 16px;">
@@ -159,29 +162,13 @@
159162
</div>
160163
161164
<div class="settings-section" data-tab="platforms">
162-
<div class="settings-section-header"> Bug Bounty Platform API Keys</div>
165+
<div class="settings-section-header"> External Aggregators &amp; Recon Keys</div>
163166
<div class="settings-section-description">
164-
Credentials for the Programs catalogue. Saved securely on the server (never
165-
shown back) and applied immediately — the Programs list refreshes in the
166-
background. Leave a field blank to keep the current value.
167+
Single-value service keys (not per-account) — an aggregator that pulls extra external
168+
programs, and a recon dataset key. Saved to the database; leave a field blank to keep
169+
the current value.
167170
</div>
168171
<div class="settings-section-body">
169-
<div class="settings-item">
170-
<div class="settings-label">
171-
<div class="settings-title">HackerOne</div>
172-
<div class="settings-hint">Username + API token (<a href="https://hackerone.com/settings/api_token" target="_blank" rel="noopener">get a token</a>). ${cfg.h1_token_set ? '<span class="badge badge-done">configured</span>' : '<span class="badge badge-failed">not set</span>'}</div>
173-
</div>
174-
<div class="settings-control" style="flex-direction:column;align-items:stretch;gap:6px;">
175-
<input type="text" id="h1-username-input" value="" placeholder="${cfg.h1_username_set ? 'username saved — type to change' : 'username'}" class="form-control premium-input">
176-
<div style="display:flex;gap:8px;">
177-
<input type="password" id="h1-token-input" value="" placeholder="${cfg.h1_token_set ? '••••••• (saved)' : 'API token'}" class="form-control premium-input" style="flex:1;">
178-
<button class="btn btn-primary" onclick="window.SettingsPage.saveH1Creds()">Save</button>
179-
</div>
180-
</div>
181-
</div>
182-
${tokenRow('Bugcrowd', 'The value of your <code>_crowdcontrol_session_key</code> cookie — from your logged-in browser (bugcrowd.com → DevTools → Application/Storage → Cookies). This is the session cookie, <em>not</em> the "API Credentials" token, which this integration can\'t use.', 'bc-token-input', 'window.SettingsPage.saveBugcrowdToken()', '_crowdcontrol_session_key value', cfg.bc_token_set)}
183-
${tokenRow('Intigriti', 'Researcher API token.', 'it-token-input', 'window.SettingsPage.saveIntigritiToken()', 'API token', cfg.it_token_set)}
184-
${tokenRow('YesWeHack', 'JWT token.', 'ywh-token-input', 'window.SettingsPage.saveYWHToken()', 'JWT token', cfg.ywh_token_set)}
185172
<div class="settings-item">
186173
<div class="settings-label">
187174
<div class="settings-title">HackAdvisor <span style="font-size:10px;color:#f472b6;font-weight:600;">external targets</span></div>

internal/app/app.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/signal"
99
"syscall"
1010

11+
"github.com/h0tak88r/AutoAR/internal/accounts"
1112
"github.com/h0tak88r/AutoAR/internal/api"
1213
"github.com/h0tak88r/AutoAR/internal/db"
1314
"github.com/h0tak88r/AutoAR/internal/envloader"
@@ -70,6 +71,10 @@ func StartAPI() error {
7071
// as the container env provides them.
7172
if os.Getenv("DB_HOST") != "" {
7273
api.HydrateEnvFromDB()
74+
// Import the legacy single-account env credentials into the DB accounts
75+
// table (one-shot) so the Settings accounts manager is the single source of
76+
// truth. Runs after HydrateEnvFromDB so UI-saved values are picked up too.
77+
accounts.MigrateEnvAccounts()
7378
}
7479

7580
// Ensure scans don't remain "running" across restarts (single-instance mode).

0 commit comments

Comments
 (0)