forked from TEAM-MAT/lockerweb
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from EATSTEAK/feat/auth-user
`auth`, `user` 엔드포인트 구현
- Loading branch information
Showing
18 changed files
with
660 additions
and
406 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 |
---|---|---|
@@ -1,51 +1,73 @@ | ||
import type { UpdateItemInput } from "aws-sdk/clients/dynamodb"; | ||
import { adminId, dynamoDB, TableName } from "../common"; | ||
import { UnauthorizedError } from "../error"; | ||
import type { GetItemInput, UpdateItemInput } from 'aws-sdk/clients/dynamodb'; | ||
import { UnauthorizedError } from '../util/error'; | ||
import { adminId, dynamoDB, TableName } from '../util/database'; | ||
import { fromUserDao } from '../user/data'; | ||
|
||
/* ISSUE/REVOKE TOKEN */ | ||
|
||
export const revokeToken = async function( | ||
id: string, | ||
token: string | ||
id: string, | ||
token: string | ||
): Promise<{ accessToken: string }> { | ||
const req: UpdateItemInput = { | ||
TableName, | ||
Key: { id: { S: id } }, | ||
UpdateExpression: 'REMOVE accessToken', | ||
ConditionExpression: 'accessToken = :token', | ||
ExpressionAttributeValues: { | ||
':token': { S: token } | ||
}, | ||
ReturnValues: 'UPDATED_OLD' | ||
}; | ||
const res = await dynamoDB.updateItem(req).promise(); | ||
if (res.Attributes.hasOwnProperty('accessToken')) { | ||
return { accessToken: token }; | ||
} else { | ||
throw new UnauthorizedError(); | ||
} | ||
const req: UpdateItemInput = { | ||
TableName, | ||
Key: { type: { S: 'user' }, id: { S: `${id}` } }, | ||
UpdateExpression: 'REMOVE aT', | ||
ConditionExpression: 'aT = :token', | ||
ExpressionAttributeValues: { | ||
':token': { S: token } | ||
}, | ||
ReturnValues: 'UPDATED_OLD' | ||
}; | ||
const res = await dynamoDB.updateItem(req).promise(); | ||
if (res.Attributes.hasOwnProperty('aT')) { | ||
return { accessToken: token }; | ||
} else { | ||
throw new UnauthorizedError(); | ||
} | ||
}; | ||
|
||
export const issueToken = async function( | ||
id: string, | ||
token: string | ||
id: string, | ||
token: string | ||
): Promise<{ id: string; expires: number }> { | ||
const expires = Date.now() + 3600 * 1000; | ||
const req: UpdateItemInput = { | ||
TableName, | ||
Key: { id: { S: id } }, | ||
UpdateExpression: 'SET accessToken = :token, expiresOn = :expiresOn', | ||
ExpressionAttributeValues: { | ||
':token': { S: token }, | ||
':expiresOn': { N: `${expires}` } | ||
}, | ||
ReturnValues: 'UPDATED_NEW' | ||
}; | ||
if (id !== adminId) { | ||
req.ConditionExpression = 'attribute_exists(is_admin) OR attribute_exists(department)'; | ||
} | ||
const res = await dynamoDB.updateItem(req).promise(); | ||
if (res.Attributes.hasOwnProperty('accessToken')) { | ||
return { id, expires }; | ||
} else { | ||
throw new UnauthorizedError('Unauthorized', { id, expires }); | ||
} | ||
}; | ||
const expires = Date.now() + 3600 * 1000 * 24; | ||
const req: UpdateItemInput = { | ||
TableName, | ||
Key: { type: { S: 'user' }, id: { S: `${id}` } }, | ||
UpdateExpression: 'SET aT = :token, eO = :expiresOn', | ||
...(id !== adminId && { ConditionExpression: 'attribute_exists(d)' }), | ||
ExpressionAttributeValues: { | ||
':token': { S: token }, | ||
':expiresOn': { N: `${expires}` } | ||
}, | ||
ReturnValues: 'UPDATED_NEW' | ||
}; | ||
const res = await dynamoDB.updateItem(req).promise(); | ||
if (res.Attributes.hasOwnProperty('aT')) { | ||
return { id, expires }; | ||
} else { | ||
throw new UnauthorizedError('Unauthorized', { id, expires }); | ||
} | ||
}; | ||
|
||
export async function assertAccessible(id: string, token: string, adminOnly = false): Promise<User> { | ||
const authReq: GetItemInput = { | ||
TableName, | ||
Key: { | ||
module: { S: 'user' }, | ||
dataId: { | ||
S: `${id}` | ||
} | ||
} | ||
}; | ||
const authRes = await dynamoDB.getItem(authReq).promise(); | ||
if ( | ||
authRes.Item.id.S !== `${id}` || | ||
authRes.Item.aT?.S !== token || | ||
(adminOnly && authRes.Item.iA?.BOOL !== true && id !== adminId) | ||
) { | ||
throw new UnauthorizedError('Unauthorized'); | ||
} | ||
return fromUserDao(authRes.Item as unknown as UserDao); | ||
} |
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,23 +1,28 @@ | ||
import type { APIGatewayProxyHandler } from "aws-lambda"; | ||
import * as jwt from "jsonwebtoken"; | ||
import { JWT_SECRET } from "../../env"; | ||
import type { JwtPayload } from "jsonwebtoken"; | ||
import { revokeToken } from "../data"; | ||
import { createResponse } from "../../common"; | ||
import type { APIGatewayProxyHandler } from 'aws-lambda'; | ||
import type { JwtPayload } from 'jsonwebtoken'; | ||
import * as jwt from 'jsonwebtoken'; | ||
import { JWT_SECRET } from '../../env'; | ||
import { assertAccessible, revokeToken } from '../data'; | ||
import { createResponse } from '../../common'; | ||
import { ResponsibleError } from '../../util/error'; | ||
|
||
export const logoutHandler: APIGatewayProxyHandler = async (event) => { | ||
const token = (event.headers.Authorization ?? '').replace('Bearer ', ''); | ||
try { | ||
const payload = jwt.verify(token, JWT_SECRET) as JwtPayload; | ||
const res = await revokeToken(payload.aud as string, token); | ||
return createResponse(200, { success: true, ...res }); | ||
} catch (err) { | ||
const res = { | ||
success: false, | ||
token, | ||
error: 401, | ||
error_description: 'Unauthorized' | ||
}; | ||
return createResponse(401, res); | ||
} | ||
const token = (event.headers.Authorization ?? '').replace('Bearer ', ''); | ||
try { | ||
const payload = jwt.verify(token, JWT_SECRET) as JwtPayload; | ||
await assertAccessible(payload.aud as string, token); | ||
const res = await revokeToken(payload.aud as string, token); | ||
return createResponse(200, { success: true, ...res }); | ||
} catch (err) { | ||
if (err instanceof ResponsibleError) { | ||
return err.response(); | ||
} | ||
const res = { | ||
success: false, | ||
token, | ||
error: 401, | ||
error_description: 'Unauthorized' | ||
}; | ||
return createResponse(401, res); | ||
} | ||
}; |
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
Oops, something went wrong.