-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce SafeSession, Session Deauthorizing, and Browser Detection
- Loading branch information
Showing
12 changed files
with
234 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
import { useHttp } from './core'; | ||
|
||
export const useSessions = () => useHttp<any>('/api/sessions'); | ||
type SessionResponse = { | ||
id: string; | ||
user_id: number; | ||
user_agent: string; | ||
user_ip: string; | ||
last_access: string; | ||
// valid: boolean; | ||
}; | ||
|
||
export const useSessions = () => useHttp<SessionResponse[]>('/api/sessions'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { FC } from 'react'; | ||
import { UAParser } from 'ua-parser-js'; | ||
|
||
import { useSessions } from '../api/sessions'; | ||
import { getRelativeTimeString } from '../util/date'; | ||
|
||
export const ActiveSessionsTable: FC = () => { | ||
const { data: sessions } = useSessions(); | ||
|
||
return ( | ||
<div className="p-2 space-y-2"> | ||
<h2 className="font-bold">Active Sessions</h2> | ||
<hr /> | ||
<p> | ||
These are the sessions that are currently active for your | ||
account. | ||
</p> | ||
<div className="space-y-2"> | ||
{sessions && | ||
sessions.map((session) => { | ||
const ua = UAParser(session.user_agent); | ||
const time = new Date(session.last_access); | ||
const x = getRelativeTimeString(time); | ||
|
||
return ( | ||
<div | ||
key={session.id} | ||
className="bg-blue-50 p-2 flex justify-between" | ||
> | ||
<div> | ||
<div className="space-x-2"> | ||
<b> | ||
{[ | ||
ua.browser.name, | ||
ua.browser.version, | ||
] | ||
.filter(Boolean) | ||
.join(' ')} | ||
</b> | ||
<span>on</span> | ||
<b> | ||
{[ | ||
ua.os.name, | ||
ua.cpu.architecture, | ||
ua.os.version, | ||
] | ||
.filter(Boolean) | ||
.join(' ')} | ||
</b> | ||
</div> | ||
<div>{session.user_ip}</div> | ||
<div>{x}</div> | ||
<div>#{session.id}</div> | ||
</div> | ||
<div className="flex items-center"> | ||
<button className="btn">Deauthorize</button> | ||
</div> | ||
</div> | ||
); | ||
})} | ||
</div> | ||
<p> | ||
If there is a session in here that you do not recognize, you can | ||
deauthorize it. | ||
</p> | ||
<hr /> | ||
<button className="btn">Log out everywhere</button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
.btn { | ||
@apply bg-white text-neutral-800 border border-neutral-200 px-2.5 py-0.5 h-fit hover:bg-neutral-50 rounded-md focus:outline focus:outline-2 outline-offset-2 outline-blue-500; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
import { createLazyFileRoute } from '@tanstack/react-router' | ||
import { FC } from 'react' | ||
|
||
import { createLazyFileRoute } from '@tanstack/react-router'; | ||
import { FC } from 'react'; | ||
|
||
const component: FC = () => { | ||
return <div className="p-2">Hello from About!</div> | ||
} | ||
return <div className="p-2">Hello from About!</div>; | ||
}; | ||
|
||
export const Route = createLazyFileRoute('/about')({ | ||
component, | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
import { createLazyFileRoute } from '@tanstack/react-router' | ||
import { createLazyFileRoute } from '@tanstack/react-router'; | ||
|
||
const component = () => { | ||
return ( | ||
<div className="p-2"> | ||
<h3>Welcome Home!</h3> | ||
</div> | ||
) | ||
} | ||
); | ||
}; | ||
|
||
export const Route = createLazyFileRoute('/')({ | ||
component | ||
}) | ||
component, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,35 @@ | ||
import { createFileRoute } from '@tanstack/react-router'; | ||
import { FC } from 'react'; | ||
|
||
import { useSessions } from '../api/sessions'; | ||
import { useAuth } from '../api/auth'; | ||
import { ActiveSessionsTable } from '../components/ActiveSessionsTable'; | ||
|
||
const component: FC = () => { | ||
const { data: sessions } = useSessions(); | ||
|
||
return ( | ||
<div className="p-2"> | ||
Hello from About! | ||
<div> | ||
{sessions && | ||
sessions.map((session: any) => ( | ||
<div key={session.id}> | ||
<span>{session.id}</span> | ||
<button>Disconnect</button> | ||
</div> | ||
))} | ||
</div> | ||
<div className="mx-auto w-full max-w-xl p-2"> | ||
<ActiveSessionsTable /> | ||
</div> | ||
); | ||
}; | ||
|
||
export const Route = createFileRoute('/sessions')({ component }); | ||
export const Route = createFileRoute('/sessions')({ | ||
component, | ||
beforeLoad: async ({ location }) => { | ||
if (useAuth.getState().token === null) { | ||
// throw redirect({ | ||
// to: 'https://localhost:3000/login' as any, | ||
// search: { | ||
// // Use the current location to power a redirect after login | ||
// // (Do not use `router.state.resolvedLocation` as it can | ||
// // potentially lag behind the actual current location) | ||
// redirect: location.href, | ||
// }, | ||
// }); | ||
|
||
// eslint-disable-next-line no-undef | ||
window.location.href = | ||
'http://localhost:3000/login?redirect=' + | ||
encodeURIComponent(location.href); | ||
} | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/** | ||
* Convert a date to a relative time string, such as | ||
* "a minute ago", "in 2 hours", "yesterday", "3 months ago", etc. | ||
* using Intl.RelativeTimeFormat | ||
*/ | ||
export function getRelativeTimeString( | ||
date: Date | number, | ||
// eslint-disable-next-line no-undef | ||
lang = navigator.language | ||
): string { | ||
// Allow dates or times to be passed | ||
const timeMs = typeof date === 'number' ? date : date.getTime(); | ||
|
||
// Get the amount of seconds between the given date and now | ||
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000); | ||
|
||
// Array reprsenting one minute, hour, day, week, month, etc in seconds | ||
const cutoffs = [ | ||
60, | ||
3600, | ||
86_400, | ||
86_400 * 7, | ||
86_400 * 30, | ||
86_400 * 365, | ||
Number.POSITIVE_INFINITY, | ||
]; | ||
|
||
// Array equivalent to the above but in the string representation of the units | ||
const units: Intl.RelativeTimeFormatUnit[] = [ | ||
'second', | ||
'minute', | ||
'hour', | ||
'day', | ||
'week', | ||
'month', | ||
'year', | ||
]; | ||
|
||
// Grab the ideal cutoff unit | ||
const unitIndex = cutoffs.findIndex( | ||
(cutoff) => cutoff > Math.abs(deltaSeconds) | ||
); | ||
|
||
// Get the divisor to divide from the seconds. E.g. if our unit is "day" our divisor | ||
// is one day in seconds, so we can divide our seconds by this to get the # of days | ||
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1; | ||
|
||
// Intl.RelativeTimeFormat do its magic | ||
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }); | ||
|
||
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]); | ||
} |