diff --git a/src/core/utils/auth.ts b/src/core/utils/auth.ts index 16ecbdb0..554a513c 100644 --- a/src/core/utils/auth.ts +++ b/src/core/utils/auth.ts @@ -13,3 +13,28 @@ export function hashStateGuid(guid: string) { hash.update(guid); return hash.digest("hex"); } + +export function newNonceWithExpiration() { + const nonceExpiration = Date.now() + 1000 * 60; + return `${newGuid()}_${nonceExpiration}}`; +} + +export function isNonceExpired(nonce: string) { + if (!nonce) { + return true; + } + + const expirationString = nonce.split("_")[1]; + + if (!expirationString) { + return true; + } + + const expirationParsed = parseInt(expirationString, 10); + + if (isNaN(expirationParsed) || expirationParsed < Date.now()) { + return true; + } + + return false; +} diff --git a/src/msha/auth/routes/auth-login-provider-callback.ts b/src/msha/auth/routes/auth-login-provider-callback.ts index e1cd7279..b6c67dd8 100644 --- a/src/msha/auth/routes/auth-login-provider-callback.ts +++ b/src/msha/auth/routes/auth-login-provider-callback.ts @@ -4,7 +4,7 @@ import * as https from "https"; import * as querystring from "querystring"; import { SWA_CLI_API_URI, SWA_CLI_APP_PROTOCOL } from "../../../core/constants"; import { DEFAULT_CONFIG } from "../../../config"; -import { hashStateGuid } from "../../../core/utils/auth"; +import { hashStateGuid, isNonceExpired } from "../../../core/utils/auth"; const getGithubAuthToken = function (codeValue: string, clientId: string, clientSecret: string) { const data = querystring.stringify({ @@ -401,6 +401,16 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess return; } + if (isNonceExpired(nonce)) { + context.res = response({ + context, + status: 401, + headers: { ["Content-Type"]: "text/plain" }, + body: "Login timed out. Please try again.", + }); + return; + } + const { clientIdSettingName, clientSecretSettingName } = customAuth?.identityProviders?.[providerName]?.registration || {}; if (!clientIdSettingName) { @@ -466,7 +476,8 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess name: "Nonce", value: "deleted", path: "/", - HttpOnly: false, + secure: true, + httpOnly: true, expires: new Date(1).toUTCString(), }, { @@ -474,6 +485,8 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess value: clientPrincipal === null ? "deleted" : btoa(JSON.stringify(clientPrincipal)), domain: DEFAULT_CONFIG.host, path: "/", + secure: true, + httpOnly: true, expires: clientPrincipal === null ? new Date(1).toUTCString() : new Date(Date.now() + 1000 * 60 * 60 * 8).toUTCString(), }, ], diff --git a/src/msha/auth/routes/auth-login-provider-custom.ts b/src/msha/auth/routes/auth-login-provider-custom.ts index b80c9d94..ab371258 100644 --- a/src/msha/auth/routes/auth-login-provider-custom.ts +++ b/src/msha/auth/routes/auth-login-provider-custom.ts @@ -2,7 +2,7 @@ import { response } from "../../../core"; import * as http from "http"; import { SWA_CLI_APP_PROTOCOL } from "../../../core/constants"; import { DEFAULT_CONFIG } from "../../../config"; -import { hashStateGuid, newGuid } from "../../../core/utils/auth"; +import { hashStateGuid, newNonceWithExpiration } from "../../../core/utils/auth"; const httpTrigger = async function (context: Context, _request: http.IncomingMessage, customAuth?: SWAConfigFileAuth) { await Promise.resolve(); @@ -43,7 +43,7 @@ const httpTrigger = async function (context: Context, _request: http.IncomingMes return; } - const state = newGuid(); + const state = newNonceWithExpiration(); const hashedState = hashStateGuid(state); const redirectUri = `${SWA_CLI_APP_PROTOCOL}://${DEFAULT_CONFIG.host}:${DEFAULT_CONFIG.port}`; @@ -60,6 +60,8 @@ const httpTrigger = async function (context: Context, _request: http.IncomingMes value: btoa(state), domain: DEFAULT_CONFIG.host, path: "/", + secure: true, + httpOnly: true, }, ], status: 302,