-
-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enh: provide a demo implementation of refresh provider (#901)
- Loading branch information
1 parent
734415b
commit c32f9b1
Showing
7 changed files
with
217 additions
and
36 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { RefreshHandler } from '../../' | ||
|
||
// You may also use a plain object with `satisfies RefreshHandler`, of course! | ||
class CustomRefreshHandler implements RefreshHandler { | ||
init(): void { | ||
console.info('Use the full power of classes to customize refreshHandler!') | ||
} | ||
|
||
destroy(): void { | ||
console.info( | ||
'Hover above class properties or go to their definition ' | ||
+ 'to learn more about how to craft a refreshHandler' | ||
) | ||
} | ||
} | ||
|
||
export default new CustomRefreshHandler() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { createError, eventHandler, getRequestHeader, readBody } from 'h3' | ||
import { sign, verify } from 'jsonwebtoken' | ||
import { type JwtPayload, SECRET, type User, extractToken, tokensByUser } from './login.post' | ||
|
||
/* | ||
* DISCLAIMER! | ||
* This is a demo implementation, please create your own handlers | ||
*/ | ||
|
||
export default eventHandler(async (event) => { | ||
const body = await readBody<{ refreshToken: string }>(event) | ||
const authorizationHeader = getRequestHeader(event, 'Authorization') | ||
const refreshToken = body.refreshToken | ||
|
||
if (!refreshToken || !authorizationHeader) { | ||
throw createError({ | ||
statusCode: 401, | ||
statusMessage: 'Unauthorized, no refreshToken or no Authorization header' | ||
}) | ||
} | ||
|
||
// Verify | ||
const decoded = verify(refreshToken, SECRET) as JwtPayload | undefined | ||
if (!decoded) { | ||
throw createError({ | ||
statusCode: 401, | ||
statusMessage: 'Unauthorized, refreshToken can\'t be verified' | ||
}) | ||
} | ||
|
||
// Get tokens | ||
const userTokens = tokensByUser.get(decoded.username) | ||
if (!userTokens) { | ||
throw createError({ | ||
statusCode: 401, | ||
statusMessage: 'Unauthorized, user is not logged in' | ||
}) | ||
} | ||
|
||
// Check against known token | ||
const requestAccessToken = extractToken(authorizationHeader) | ||
const knownAccessToken = userTokens.refresh.get(body.refreshToken) | ||
if (!knownAccessToken || knownAccessToken !== requestAccessToken) { | ||
console.log({ | ||
msg: 'Tokens mismatch', | ||
knownAccessToken, | ||
requestAccessToken | ||
}) | ||
throw createError({ | ||
statusCode: 401, | ||
statusMessage: 'Tokens mismatch - this is not good' | ||
}) | ||
} | ||
|
||
// Invalidate old access token | ||
userTokens.access.delete(knownAccessToken) | ||
|
||
const user: User = { | ||
username: decoded.username, | ||
picture: decoded.picture, | ||
name: decoded.name | ||
} | ||
|
||
const accessToken = sign({ ...user, scope: ['test', 'user'] }, SECRET, { | ||
expiresIn: 60 * 5 // 5 minutes | ||
}) | ||
userTokens.refresh.set(refreshToken, accessToken) | ||
userTokens.access.set(accessToken, refreshToken) | ||
|
||
return { | ||
token: { | ||
accessToken, | ||
refreshToken | ||
} | ||
} | ||
}) |
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,32 +1,41 @@ | ||
import type { H3Event } from 'h3' | ||
import { createError, eventHandler, getRequestHeader } from 'h3' | ||
import { verify } from 'jsonwebtoken' | ||
import { SECRET } from './login.post' | ||
import { type JwtPayload, SECRET, extractToken, tokensByUser } from './login.post' | ||
|
||
const TOKEN_TYPE = 'Bearer' | ||
|
||
function extractToken(authHeaderValue: string) { | ||
const [, token] = authHeaderValue.split(`${TOKEN_TYPE} `) | ||
return token | ||
} | ||
|
||
function ensureAuth(event: H3Event) { | ||
const authHeaderValue = getRequestHeader(event, 'authorization') | ||
if (typeof authHeaderValue === 'undefined') { | ||
export default eventHandler((event) => { | ||
const authorizationHeader = getRequestHeader(event, 'Authorization') | ||
if (typeof authorizationHeader === 'undefined') { | ||
throw createError({ statusCode: 403, statusMessage: 'Need to pass valid Bearer-authorization header to access this endpoint' }) | ||
} | ||
|
||
const extractedToken = extractToken(authHeaderValue) | ||
const extractedToken = extractToken(authorizationHeader) | ||
let decoded: JwtPayload | ||
try { | ||
return verify(extractedToken, SECRET) | ||
decoded = verify(extractedToken, SECRET) as JwtPayload | ||
} | ||
catch (error) { | ||
console.error('Login failed. Here\'s the raw error:', error) | ||
console.error({ | ||
msg: 'Login failed. Here\'s the raw error:', | ||
error | ||
}) | ||
throw createError({ statusCode: 403, statusMessage: 'You must be logged in to use this endpoint' }) | ||
} | ||
} | ||
|
||
export default eventHandler((event) => { | ||
const user = ensureAuth(event) | ||
return user | ||
// Check against known token | ||
const userTokens = tokensByUser.get(decoded.username) | ||
if (!userTokens || !userTokens.access.has(extractedToken)) { | ||
throw createError({ | ||
statusCode: 401, | ||
statusMessage: 'Unauthorized, user is not logged in' | ||
}) | ||
} | ||
|
||
// All checks successful | ||
const { username, name, picture, scope } = decoded | ||
return { | ||
username, | ||
name, | ||
picture, | ||
scope | ||
} | ||
}) |