feat: maintain logged in sesssion
All checks were successful
Release / build_and_release (push) Successful in 43s
All checks were successful
Release / build_and_release (push) Successful in 43s
This commit is contained in:
parent
a9bdd1f95e
commit
2ed092bcbd
@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import { NostrController } from './controllers'
|
||||
import { AuthController, NostrController } from './controllers'
|
||||
import { MainLayout } from './layouts/Main'
|
||||
import { LandingPage } from './pages/landing/LandingPage'
|
||||
import { privateRoutes, publicRoutes } from './routes'
|
||||
@ -13,6 +13,9 @@ const App = () => {
|
||||
|
||||
useEffect(() => {
|
||||
generateBunkerDelegatedKey()
|
||||
|
||||
const authController = new AuthController()
|
||||
authController.checkSession()
|
||||
}, [])
|
||||
|
||||
const generateBunkerDelegatedKey = () => {
|
||||
|
@ -18,7 +18,11 @@ import { Link, useNavigate } from 'react-router-dom'
|
||||
import nostrichAvatar from '../../assets/images/avatar.png'
|
||||
import nostrichLogo from '../../assets/images/nostr-logo.jpg'
|
||||
import { appPublicRoutes, getProfileRoute } from '../../routes'
|
||||
import { saveNsecBunkerDelegatedKey, shorten } from '../../utils'
|
||||
import {
|
||||
clearAuthToken,
|
||||
saveNsecBunkerDelegatedKey,
|
||||
shorten
|
||||
} from '../../utils'
|
||||
import styles from './style.module.scss'
|
||||
import { NostrController } from '../../controllers'
|
||||
|
||||
@ -67,6 +71,9 @@ export const AppBar = () => {
|
||||
})
|
||||
)
|
||||
|
||||
// clear authToken saved in local storage
|
||||
clearAuthToken()
|
||||
|
||||
// update nsecBunker delegated key after logout
|
||||
const nostrController = NostrController.getInstance()
|
||||
const newDelegatedKey = nostrController.generateDelegatedKey()
|
||||
|
@ -2,8 +2,15 @@ import { EventTemplate } from 'nostr-tools'
|
||||
import { MetadataController, NostrController } from '.'
|
||||
import { setAuthState, setMetadataEvent } from '../store/actions'
|
||||
import store from '../store/store'
|
||||
import { getVisitedLink } from '../utils'
|
||||
import {
|
||||
base64DecodeAuthToken,
|
||||
base64EncodeSignedEvent,
|
||||
getAuthToken,
|
||||
getVisitedLink,
|
||||
saveAuthToken
|
||||
} from '../utils'
|
||||
import { appPrivateRoutes } from '../routes'
|
||||
import { AuthToken, SignedEvent } from '../types'
|
||||
|
||||
export class AuthController {
|
||||
private nostrController: NostrController
|
||||
@ -44,7 +51,8 @@ export class AuthController {
|
||||
created_at: timestamp
|
||||
}
|
||||
|
||||
await this.nostrController.signEvent(authEvent)
|
||||
const signedAuthEvent = await this.nostrController.signEvent(authEvent)
|
||||
this.createAndSaveAuthToken(signedAuthEvent)
|
||||
|
||||
store.dispatch(
|
||||
setAuthState({
|
||||
@ -64,4 +72,50 @@ export class AuthController {
|
||||
return Promise.resolve(appPrivateRoutes.homePage)
|
||||
}
|
||||
}
|
||||
|
||||
checkSession() {
|
||||
const savedAuthToken = getAuthToken()
|
||||
|
||||
if (savedAuthToken && this.isTokenValid(savedAuthToken)) {
|
||||
const signedEvent = base64DecodeAuthToken(savedAuthToken.token)
|
||||
|
||||
store.dispatch(
|
||||
setAuthState({
|
||||
loggedIn: true,
|
||||
usersPubkey: signedEvent.pubkey
|
||||
})
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
setAuthState({
|
||||
loggedIn: false,
|
||||
usersPubkey: undefined
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private isTokenValid(authToken: AuthToken): boolean {
|
||||
const timeNow = Math.round(Date.now() / 1000)
|
||||
const eventExpiresAt = authToken.expiresAt
|
||||
const timeDifference = eventExpiresAt - timeNow
|
||||
|
||||
// check if previous authToken has expired or not
|
||||
if (timeDifference > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private createAndSaveAuthToken(signedAuthEvent: SignedEvent) {
|
||||
const base64Encoded = base64EncodeSignedEvent(signedAuthEvent)
|
||||
|
||||
// save newly created auth token (base64 nostr singed event) in local storage along with expiry time
|
||||
const createdAt = Math.round(Date.now() / 1000)
|
||||
saveAuthToken(base64Encoded, createdAt + 3600) // 3600 secs = 1 hour
|
||||
return base64Encoded
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ import { saveState } from './utils'
|
||||
store.subscribe(
|
||||
_.throttle(() => {
|
||||
saveState({
|
||||
auth: store.getState().auth
|
||||
auth: store.getState().auth,
|
||||
metadata: store.getState().metadata
|
||||
})
|
||||
}, 1000)
|
||||
)
|
||||
|
4
src/types/auth.ts
Normal file
4
src/types/auth.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface AuthToken {
|
||||
token: string
|
||||
expiresAt: number
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export * from './auth'
|
||||
export * from './nostr'
|
||||
export * from './profile'
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { State } from '../store/rootReducer'
|
||||
import { AuthToken } from '../types'
|
||||
|
||||
export const saveState = (state: object) => {
|
||||
try {
|
||||
@ -53,3 +54,29 @@ export const getVisitedLink = () => {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const saveAuthToken = (token: string, expiresAt: number) => {
|
||||
localStorage.setItem(
|
||||
'authToken',
|
||||
JSON.stringify({
|
||||
token,
|
||||
expiresAt
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const getAuthToken = () => {
|
||||
const serializedAuthDetail = localStorage.getItem('authToken')
|
||||
|
||||
if (!serializedAuthDetail) return null
|
||||
|
||||
try {
|
||||
return JSON.parse(serializedAuthDetail) as AuthToken
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const clearAuthToken = () => {
|
||||
localStorage.removeItem('authToken')
|
||||
}
|
||||
|
@ -124,3 +124,24 @@ export const queryNip05 = async (
|
||||
relays
|
||||
}
|
||||
}
|
||||
|
||||
export const base64EncodeSignedEvent = (event: SignedEvent) => {
|
||||
try {
|
||||
const authEventSerialized = JSON.stringify(event)
|
||||
const token = btoa(authEventSerialized)
|
||||
return token
|
||||
} catch (error) {
|
||||
throw new Error('An error occurred in JSON.stringify of signedAuthEvent')
|
||||
}
|
||||
}
|
||||
|
||||
export const base64DecodeAuthToken = (authToken: string): SignedEvent => {
|
||||
const decodedToken = atob(authToken)
|
||||
|
||||
try {
|
||||
const signedEvent = JSON.parse(decodedToken)
|
||||
return signedEvent
|
||||
} catch (error) {
|
||||
throw new Error('An error occurred in JSON.parse of the auth token')
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user