From 1cb0873ce8bd88772712d198295c4ce2073b3b67 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sat, 12 Oct 2019 18:29:00 +0200 Subject: [PATCH 1/3] Adding $local callback endpoint --- src/api/oauth/auth.ts | 6 +++++- src/views/views.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/api/oauth/auth.ts b/src/api/oauth/auth.ts index dbcce8d..ec513c3 100644 --- a/src/api/oauth/auth.ts +++ b/src/api/oauth/auth.ts @@ -12,6 +12,8 @@ import { ObjectID } from "bson"; const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => { let { response_type, client_id, redirect_uri, scope, state, nored } = req.query; const sendError = (type) => { + if (redirect_uri === "$local") + redirect_uri = "/code"; res.redirect(redirect_uri += `?error=${type}&state=${state}`); } /** @@ -63,7 +65,9 @@ const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Res }); await ClientCode.save(code); - let ruri = client.redirect_url + `?code=${code.code}&state=${state}`; + let redir = client.redirect_url === "$local" ? "/code" : client.redirect_url; + + let ruri = redir + `?code=${code.code}&state=${state}`; if (nored === "true") { res.json({ redirect_uri: ruri diff --git a/src/views/views.ts b/src/views/views.ts index 6a31114..9bb2638 100644 --- a/src/views/views.ts +++ b/src/views/views.ts @@ -34,6 +34,14 @@ ViewRouter.get("/login", (req, res) => { res.send(GetLoginPage(req.__)) }) +ViewRouter.get("/code", (req, res) => { + res.setHeader("Cache-Control", "no-cache"); + if (req.query.error) + res.send("Some error occured: " + req.query.error); + else + res.send(`Your code is: ${req.query.code}`); +}) + ViewRouter.get("/admin", GetUserMiddleware(false, true), (req: Request, res, next) => { if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN) else next() From f604f4f13dbcf0e68fdd825c6c587ff942708540 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sat, 12 Oct 2019 20:00:26 +0200 Subject: [PATCH 2/3] Make view cancel button work with new $local redirect_uri --- views/src/authorize/authorize.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/src/authorize/authorize.js b/views/src/authorize/authorize.js index 8c6ccb2..248682c 100644 --- a/views/src/authorize/authorize.js +++ b/views/src/authorize/authorize.js @@ -7,9 +7,12 @@ function submit() { document.getElementById("cancel").onclick = () => { let u = new URL(window.location); let uri = u.searchParams.get("redirect_uri"); + if (uri === "$local") { + uri = "/code"; + } window.location.href = uri + "?error=access_denied&state=" + u.searchParams.get("state"); } document.getElementById("allow").onclick = () => { submit() -} +} \ No newline at end of file From a39d73537f104d8ecd5d16c927f923e3949ed7f6 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sat, 12 Oct 2019 21:35:21 +0200 Subject: [PATCH 3/3] Enforcing authorization_code vaild date check --- src/api/oauth/refresh.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/api/oauth/refresh.ts b/src/api/oauth/refresh.ts index ccea5ce..84d6648 100644 --- a/src/api/oauth/refresh.ts +++ b/src/api/oauth/refresh.ts @@ -13,12 +13,24 @@ import { JWTExpDur } from "../../keys"; import RefreshToken from "../../models/refresh_token"; import { getEncryptionKey } from "../../helper/user_key"; +// TODO: +/* +For example, the authorization server could employ refresh token +rotation in which a new refresh token is issued with every access +token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is +compromised and subsequently used by both the attacker and the +legitimate client, one of them will present an invalidated refresh +token, which will inform the authorization server of the breach. +*/ + +const refreshTokenValidTime = moment.duration(6, "month"); + const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), async (req: Request, res: Response) => { let grant_type = req.query.grant_type || req.body.grant_type; if (!grant_type || grant_type === "authorization_code") { let code = req.query.code || req.body.code; let c = await ClientCode.findOne({ code: code }) - if (!c) { + if (!c || moment(c.validTill).isBefore()) { throw new RequestError(req.__("Invalid code"), HttpStatusCode.BAD_REQUEST); } @@ -33,7 +45,7 @@ const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), a permissions: c.permissions, token: randomBytes(16).toString("hex"), valid: true, - validTill: moment().add(6, "months").toDate() + validTill: moment().add(refreshTokenValidTime).toDate() }); await RefreshToken.save(token); await ClientCode.delete(c); @@ -63,7 +75,11 @@ const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), a if (!refresh_token) throw new RequestError(req.__("refresh_token not set"), HttpStatusCode.BAD_REQUEST); let token = await RefreshToken.findOne({ token: refresh_token }); - if (!token) throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST); + if (!token || !token.valid || moment(token.validTill).isBefore()) + throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST); + + token.validTill = moment().add(refreshTokenValidTime).toDate(); + await RefreshToken.save(token); let user = await User.findById(token.user); let client = await Client.findById(token.client)