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 { useEffect } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { NostrController } from './controllers'
|
import { AuthController, NostrController } from './controllers'
|
||||||
import { MainLayout } from './layouts/Main'
|
import { MainLayout } from './layouts/Main'
|
||||||
import { LandingPage } from './pages/landing/LandingPage'
|
import { LandingPage } from './pages/landing/LandingPage'
|
||||||
import { privateRoutes, publicRoutes } from './routes'
|
import { privateRoutes, publicRoutes } from './routes'
|
||||||
@ -13,6 +13,9 @@ const App = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
generateBunkerDelegatedKey()
|
generateBunkerDelegatedKey()
|
||||||
|
|
||||||
|
const authController = new AuthController()
|
||||||
|
authController.checkSession()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const generateBunkerDelegatedKey = () => {
|
const generateBunkerDelegatedKey = () => {
|
||||||
|
@ -18,7 +18,11 @@ import { Link, useNavigate } from 'react-router-dom'
|
|||||||
import nostrichAvatar from '../../assets/images/avatar.png'
|
import nostrichAvatar from '../../assets/images/avatar.png'
|
||||||
import nostrichLogo from '../../assets/images/nostr-logo.jpg'
|
import nostrichLogo from '../../assets/images/nostr-logo.jpg'
|
||||||
import { appPublicRoutes, getProfileRoute } from '../../routes'
|
import { appPublicRoutes, getProfileRoute } from '../../routes'
|
||||||
import { saveNsecBunkerDelegatedKey, shorten } from '../../utils'
|
import {
|
||||||
|
clearAuthToken,
|
||||||
|
saveNsecBunkerDelegatedKey,
|
||||||
|
shorten
|
||||||
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { NostrController } from '../../controllers'
|
import { NostrController } from '../../controllers'
|
||||||
|
|
||||||
@ -67,6 +71,9 @@ export const AppBar = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// clear authToken saved in local storage
|
||||||
|
clearAuthToken()
|
||||||
|
|
||||||
// update nsecBunker delegated key after logout
|
// update nsecBunker delegated key after logout
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const newDelegatedKey = nostrController.generateDelegatedKey()
|
const newDelegatedKey = nostrController.generateDelegatedKey()
|
||||||
|
@ -2,8 +2,15 @@ import { EventTemplate } from 'nostr-tools'
|
|||||||
import { MetadataController, NostrController } from '.'
|
import { MetadataController, NostrController } from '.'
|
||||||
import { setAuthState, setMetadataEvent } from '../store/actions'
|
import { setAuthState, setMetadataEvent } from '../store/actions'
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { getVisitedLink } from '../utils'
|
import {
|
||||||
|
base64DecodeAuthToken,
|
||||||
|
base64EncodeSignedEvent,
|
||||||
|
getAuthToken,
|
||||||
|
getVisitedLink,
|
||||||
|
saveAuthToken
|
||||||
|
} from '../utils'
|
||||||
import { appPrivateRoutes } from '../routes'
|
import { appPrivateRoutes } from '../routes'
|
||||||
|
import { AuthToken, SignedEvent } from '../types'
|
||||||
|
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
private nostrController: NostrController
|
private nostrController: NostrController
|
||||||
@ -44,7 +51,8 @@ export class AuthController {
|
|||||||
created_at: timestamp
|
created_at: timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.nostrController.signEvent(authEvent)
|
const signedAuthEvent = await this.nostrController.signEvent(authEvent)
|
||||||
|
this.createAndSaveAuthToken(signedAuthEvent)
|
||||||
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
setAuthState({
|
setAuthState({
|
||||||
@ -64,4 +72,50 @@ export class AuthController {
|
|||||||
return Promise.resolve(appPrivateRoutes.homePage)
|
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(
|
store.subscribe(
|
||||||
_.throttle(() => {
|
_.throttle(() => {
|
||||||
saveState({
|
saveState({
|
||||||
auth: store.getState().auth
|
auth: store.getState().auth,
|
||||||
|
metadata: store.getState().metadata
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 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 './nostr'
|
||||||
export * from './profile'
|
export * from './profile'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { State } from '../store/rootReducer'
|
import { State } from '../store/rootReducer'
|
||||||
|
import { AuthToken } from '../types'
|
||||||
|
|
||||||
export const saveState = (state: object) => {
|
export const saveState = (state: object) => {
|
||||||
try {
|
try {
|
||||||
@ -53,3 +54,29 @@ export const getVisitedLink = () => {
|
|||||||
return null
|
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
|
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