mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #751 from PR0M3TH3AN/codex/create-authproviders-and-update-authservice
Add pluggable auth provider registry
This commit is contained in:
5 files changed
+240
-27
No files matched your search
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in new issue
Block a user