import { EventTemplate } from 'nostr-tools' import { MetadataController, NostrController } from '.' import { setAuthState, setMetadataEvent, setRelayMapAction } from '../store/actions' import store from '../store/store' import { base64DecodeAuthToken, base64EncodeSignedEvent, getAuthToken, getVisitedLink, saveAuthToken, compareObjects } from '../utils' import { appPrivateRoutes } from '../routes' import { SignedEvent } from '../types' export class AuthController { private nostrController: NostrController private metadataController: MetadataController constructor() { this.nostrController = NostrController.getInstance() this.metadataController = new MetadataController() } /** * Function will authenticate user by signing an auth event * which is done by calling the sign() function, where appropriate * method will be chosen (extension, nsecbunker or keys) * * @param pubkey of the user trying to login * @returns url to redirect if authentication successfull * or error if otherwise */ async authAndGetMetadataAndRelaysMap(pubkey: string) { const emptyMetadata = this.metadataController.getEmptyMetadataEvent() this.metadataController .findMetadata(pubkey) .then((event) => { if (event) { store.dispatch(setMetadataEvent(event)) } else { store.dispatch(setMetadataEvent(emptyMetadata)) } }) .catch((err) => { console.warn('Error occurred while finding metadata', err) store.dispatch(setMetadataEvent(emptyMetadata)) }) // Nostr uses unix timestamps const timestamp = Math.floor(Date.now() / 1000) const { hostname } = window.location const authEvent: EventTemplate = { kind: 27235, tags: [], content: `${hostname}-${timestamp}`, created_at: timestamp } const signedAuthEvent = await this.nostrController.signEvent(authEvent) this.createAndSaveAuthToken(signedAuthEvent) store.dispatch( setAuthState({ loggedIn: true, usersPubkey: pubkey }) ) const relayMap = await this.nostrController.getRelayMap(pubkey) if (Object.keys(relayMap).length < 1) { // Navigate user to relays page if relay map is empty return Promise.resolve(appPrivateRoutes.relays) } if (store.getState().auth?.loggedIn) { if (!compareObjects(store.getState().relays?.map, relayMap.map)) store.dispatch(setRelayMapAction(relayMap.map)) } const currentLocation = window.location.hash.replace('#', '') if (!Object.values(appPrivateRoutes).includes(currentLocation)) { // User did change the location to one of the private routes const visitedLink = getVisitedLink() if (visitedLink) { const { pathname, search } = visitedLink return Promise.resolve(`${pathname}${search}`) } else { // Navigate user in return Promise.resolve(appPrivateRoutes.homePage) } } } checkSession() { const savedAuthToken = getAuthToken() if (savedAuthToken) { const signedEvent = base64DecodeAuthToken(savedAuthToken) store.dispatch( setAuthState({ loggedIn: true, usersPubkey: signedEvent.pubkey }) ) return } store.dispatch( setAuthState({ loggedIn: false, usersPubkey: undefined }) ) } private createAndSaveAuthToken(signedAuthEvent: SignedEvent) { const base64Encoded = base64EncodeSignedEvent(signedAuthEvent) // save newly created auth token (base64 nostr singed event) in local storage along with expiry time saveAuthToken(base64Encoded) return base64Encoded } }