Merge pull request #751 from PR0M3TH3AN/codex/create-authproviders-and-update-authservice

Add pluggable auth provider registry
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-21 09:47:38 -04:00
commit 147e57845b
5 files changed
+240 -27

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
import nip07Provider from "./nip07.js";
const providers = [nip07Provider].filter(
(provider) => provider && typeof provider.id === "string",
);
const providersById = providers.reduce((acc, provider) => {
const key = provider.id;
if (!acc[key]) {
acc[key] = provider;
}
return acc;
}, Object.create(null));
export function getAuthProvider(providerId) {
if (typeof providerId !== "string") {
return null;
}
const trimmed = providerId.trim();
if (!trimmed) {
return null;
}
return providersById[trimmed] || null;
}
export function listAuthProviders() {
return providers.slice();
}
export { providersById };
export default providers;
+36
View File
@@ -0,0 +1,36 @@
const PROVIDER_ID = "nip07";
const PROVIDER_LABEL = "NIP-07 browser extension";
async function login({ nostrClient, options } = {}) {
if (!nostrClient || typeof nostrClient.login !== "function") {
throw new Error("NIP-07 login is not available.");
}
const normalizedOptions =
options && typeof options === "object" ? options : {};
const result = await nostrClient.login(normalizedOptions);
let pubkey = "";
let signer = null;
if (typeof result === "string") {
pubkey = result;
} else if (result && typeof result === "object") {
if (typeof result.pubkey === "string") {
pubkey = result.pubkey;
} else if (typeof result.publicKey === "string") {
pubkey = result.publicKey;
}
if (result.signer !== undefined) {
signer = result.signer;
}
}
return { authType: PROVIDER_ID, pubkey, signer };
}
export default Object.freeze({
id: PROVIDER_ID,
label: PROVIDER_LABEL,
login,
});
+137 -23
View File
@@ -19,6 +19,7 @@ import {
getProfileCacheEntry as getCachedProfileEntry,
setProfileCacheEntry as setCachedProfileEntry,
} from "../state/cache.js";
import { getAuthProvider } from "./authProviders/index.js";
class SimpleEventEmitter {
constructor(logger = null) {
@@ -163,12 +164,42 @@ export default class AuthService {
return Array.isArray(saved) ? saved.map((entry) => ({ ...entry })) : [];
}
emitProfileList(reason = "update") {
this.emit("profile:updated", {
emitProfileList(reason = "update", extraDetail = null) {
const savedProfiles = this.cloneSavedProfiles();
const activeProfilePubkey = getActiveProfilePubkey();
const detail = {
reason,
savedProfiles: this.cloneSavedProfiles(),
activeProfilePubkey: getActiveProfilePubkey(),
});
savedProfiles,
activeProfilePubkey,
};
if (extraDetail && typeof extraDetail === "object") {
Object.assign(detail, extraDetail);
}
if (!("activeProfileAuthType" in detail)) {
const normalizedActive = this.normalizeHexPubkey(activeProfilePubkey);
if (normalizedActive) {
const activeEntry = savedProfiles.find((entry) => {
const normalizedEntry = this.normalizeHexPubkey(entry?.pubkey);
return normalizedEntry && normalizedEntry === normalizedActive;
});
if (
activeEntry &&
typeof activeEntry.authType === "string" &&
activeEntry.authType.trim()
) {
detail.activeProfileAuthType = activeEntry.authType;
} else {
detail.activeProfileAuthType = null;
}
} else {
detail.activeProfileAuthType = null;
}
}
this.emit("profile:updated", detail);
}
safeEncodeNpub(pubkey) {
@@ -239,32 +270,72 @@ export default class AuthService {
}
async requestLogin(options = {}) {
if (!this.nostrClient || typeof this.nostrClient.login !== "function") {
throw new Error("Nostr login is not available.");
const normalizedOptions =
options && typeof options === "object" ? { ...options } : {};
const {
providerId: rawProviderId,
autoApply,
persistActive: persistActiveOption,
...providerOptions
} = normalizedOptions;
const providerId =
typeof rawProviderId === "string" && rawProviderId.trim()
? rawProviderId.trim()
: "nip07";
const provider = getAuthProvider(providerId);
if (!provider || typeof provider.login !== "function") {
throw new Error(`Unknown auth provider: ${providerId}`);
}
const result = await this.nostrClient.login(options);
const pubkey =
typeof result === "string"
? result
: result && typeof result === "object"
? result.pubkey || result.publicKey || ""
: "";
const providerResult = await provider.login({
nostrClient: this.nostrClient,
options: providerOptions,
});
let pubkey = "";
let signer = null;
if (typeof providerResult === "string") {
pubkey = providerResult;
} else if (providerResult && typeof providerResult === "object") {
if (typeof providerResult.pubkey === "string") {
pubkey = providerResult.pubkey;
} else if (typeof providerResult.publicKey === "string") {
pubkey = providerResult.publicKey;
}
if (providerResult.signer !== undefined) {
signer = providerResult.signer;
}
}
const trimmed = typeof pubkey === "string" ? pubkey.trim() : "";
const authType =
providerResult &&
typeof providerResult === "object" &&
typeof providerResult.authType === "string" &&
providerResult.authType.trim()
? providerResult.authType.trim()
: provider.id;
if (!trimmed) {
return { pubkey: null };
return { pubkey: null, authType, signer, providerId };
}
if (options?.autoApply === false) {
return { pubkey: trimmed };
if (autoApply === false) {
return { pubkey: trimmed, authType, signer, providerId };
}
const detail = await this.login(trimmed, {
persistActive: options?.persistActive !== false,
persistActive: persistActiveOption !== false,
authType,
signer,
providerId,
});
return { pubkey: trimmed, detail };
return { pubkey: trimmed, detail, authType, signer, providerId };
}
async handleUploadSubmit(payload, { publish } = {}) {
@@ -292,6 +363,8 @@ export default class AuthService {
const persistActive =
normalizedOptions.persistActive === false ? false : true;
const existingProfiles = getSavedProfiles();
const previousPubkey = this.normalizeHexPubkey(getPubkey()) || getPubkey();
const normalized = this.normalizeHexPubkey(pubkey);
const trimmed = typeof pubkey === "string" ? pubkey.trim() : "";
@@ -300,6 +373,42 @@ export default class AuthService {
throw new Error("A valid pubkey is required for login.");
}
const normalizedAuthType = (() => {
if (
typeof normalizedOptions.authType === "string" &&
normalizedOptions.authType.trim()
) {
return normalizedOptions.authType.trim();
}
if (Array.isArray(existingProfiles)) {
const normalizedNext = normalized || this.normalizeHexPubkey(trimmed);
if (normalizedNext) {
const existingEntry = existingProfiles.find((entry) => {
const normalizedEntry = this.normalizeHexPubkey(entry?.pubkey);
return normalizedEntry && normalizedEntry === normalizedNext;
});
if (
existingEntry &&
typeof existingEntry.authType === "string" &&
existingEntry.authType.trim()
) {
return existingEntry.authType.trim();
}
}
}
return "nip07";
})();
const normalizedSigner =
normalizedOptions.signer !== undefined ? normalizedOptions.signer : null;
const providerId =
typeof normalizedOptions.providerId === "string" &&
normalizedOptions.providerId.trim()
? normalizedOptions.providerId.trim()
: null;
const identityChanged = previousPubkey !== nextPubkey;
if (normalized) {
@@ -333,7 +442,7 @@ export default class AuthService {
npub: npub || (cachedProfile.npub ?? null),
name: cachedProfile.name || draft[existingIndex]?.name || "",
picture: cachedProfile.picture || draft[existingIndex]?.picture || "",
authType: "nip07",
authType: normalizedAuthType,
};
if (existingIndex >= 0) {
@@ -365,9 +474,11 @@ export default class AuthService {
persistSavedProfiles({ persistActive: true });
}
if (savedProfilesMutated) {
this.emitProfileList("login");
}
this.emitProfileList("login", {
authType: normalizedAuthType,
signer: normalizedSigner,
providerId,
});
const activeLoginPubkey = getPubkey();
const normalizedLoginPubkey =
@@ -380,6 +491,9 @@ export default class AuthService {
identityChanged,
savedProfiles: this.cloneSavedProfiles(),
activeProfilePubkey: getActiveProfilePubkey(),
authType: normalizedAuthType,
signer: normalizedSigner,
providerId,
postLogin: {
pubkey: normalizedLoginPubkey,
blocksLoaded: false,
+8 -2
View File
@@ -267,7 +267,10 @@ export function persistSavedProfiles({ persistActive = true } = {}) {
typeof entry.npub === "string" && entry.npub.trim() ? entry.npub.trim() : null,
name: typeof entry.name === "string" ? entry.name : "",
picture: typeof entry.picture === "string" ? entry.picture : "",
authType: entry.authType === "nsec" ? "nsec" : "nip07",
authType:
typeof entry.authType === "string" && entry.authType.trim()
? entry.authType.trim()
: "nip07",
})),
activePubkey: activePubkeyToPersist,
};
@@ -306,7 +309,10 @@ export function loadSavedProfilesFromStorage() {
}
seenPubkeys.add(normalizedPubkey);
const storedAuthType = candidate.authType === "nsec" ? "nsec" : "nip07";
const storedAuthType =
typeof candidate.authType === "string" && candidate.authType.trim()
? candidate.authType.trim()
: "nip07";
const npub =
typeof candidate.npub === "string" && candidate.npub.trim()
? candidate.npub.trim()
+28 -2
View File
@@ -5,6 +5,7 @@ import {
BITVID_WEBSITE_URL as CONFIG_BITVID_WEBSITE_URL,
MAX_WALLET_DEFAULT_ZAP as CONFIG_MAX_WALLET_DEFAULT_ZAP,
} from "../config.js";
import { getAuthProvider } from "../services/authProviders/index.js";
const noop = () => {};
@@ -15,6 +16,32 @@ const DEFAULT_BITVID_WEBSITE_URL = "https://bitvid.network/";
const NWC_URI_SCHEME = "nostr+walletconnect://";
const DEFAULT_MAX_WALLET_DEFAULT_ZAP = 100000000;
function resolveAuthTypeLabel(authType) {
if (typeof authType !== "string") {
return "Saved profile";
}
const trimmed = authType.trim();
if (!trimmed) {
return "Saved profile";
}
const provider = getAuthProvider(trimmed);
if (provider && typeof provider.label === "string" && provider.label) {
return provider.label;
}
if (trimmed === "nsec") {
return "Direct key";
}
if (trimmed === "nip07") {
return "Saved profile";
}
return trimmed;
}
const DEFAULT_INTERNAL_NWC_SETTINGS = Object.freeze({
nwcUri: "",
defaultZap: null,
@@ -1306,8 +1333,7 @@ export class ProfileModalController {
const label = document.createElement("span");
label.className = "profile-card__label";
label.textContent =
entry.authType === "nsec" ? "Direct key" : "Saved profile";
label.textContent = resolveAuthTypeLabel(entry.authType);
const action = document.createElement("span");
action.className = "profile-card__action";