зеркало из
1
0
Форкнуть 0

Use StaticWebAppsAuthContextCookie instead of just Nonce cookie and support honor post_login_redirect_uri

This commit is contained in:
Aniello Scotto Di Marco 2024-05-06 16:12:11 -07:00
Родитель fcf764cc1b
Коммит 75211b2ed4
6 изменённых файлов: 49 добавлений и 26 удалений

Просмотреть файл

@ -41,7 +41,7 @@ export type SWACommand = typeof SWA_COMMANDS[number];
export const SWA_RUNTIME_CONFIG_MAX_SIZE_IN_KB = 20; // 20kb
export const NONCE = `Nonce`;
export const SWA_AUTH_CONTEXT_COOKIE = `StaticWebAppsAuthContextCookie`;
export const SWA_AUTH_COOKIE = `StaticWebAppsAuthCookie`;
export const ALLOWED_HTTP_METHODS_FOR_STATIC_CONTENT = ["GET", "HEAD", "OPTIONS"];

Просмотреть файл

@ -38,3 +38,14 @@ export function isNonceExpired(nonce: string) {
return false;
}
export function extractPostLoginRedirectUri(protocol?: string, host?: string, path?: string) {
if (!!protocol && !!host && !!path) {
try {
const url = new URL(`${protocol}://${host}${path}`);
return url.searchParams.get("post_login_redirect_uri") ?? undefined;
} catch {}
}
return undefined;
}

Просмотреть файл

@ -1,6 +1,6 @@
import chalk from "chalk";
import cookie from "cookie";
import { NONCE, SWA_AUTH_COOKIE } from "../constants";
import { SWA_AUTH_CONTEXT_COOKIE, SWA_AUTH_COOKIE } from "../constants";
import { logger } from "./logger";
/**
@ -35,7 +35,7 @@ export function serializeCookie(cookieName: string, cookieValue: string, options
* @returns A ClientPrincipal object.
*/
export function decodeCookie(cookieValue: string): ClientPrincipal | null {
logger.silly(`decoding cookie`);
logger.silly(`decoding StaticWebAppsAuthCookie cookie`);
const cookies = cookie.parse(cookieValue);
if (cookies[SWA_AUTH_COOKIE]) {
const decodedValue = Buffer.from(cookies[SWA_AUTH_COOKIE], "base64").toString();
@ -47,32 +47,32 @@ export function decodeCookie(cookieValue: string): ClientPrincipal | null {
}
/**
* Check if the Nonce is available.
* Check if the StaticWebAppsAuthContextCookie is available.
* @param cookieValue The cookie value.
* @returns True if Nonce is found. False otherwise.
* @returns True if StaticWebAppsAuthContextCookie is found. False otherwise.
*/
export function validateNonceCookie(cookieValue: string | number | string[]) {
export function validateAuthContextCookie(cookieValue: string | number | string[]) {
if (typeof cookieValue !== "string") {
throw Error(`TypeError: cookie value must be a string`);
}
const cookies = cookie.parse(cookieValue);
return !!cookies[NONCE];
return !!cookies[SWA_AUTH_CONTEXT_COOKIE];
}
/**
*
* @param cookieValue
* @returns Nonce string.
* @returns StaticWebAppsAuthContextCookie string.
*/
export function decodeNonceCookie(cookieValue: string): string | null {
logger.silly(`decoding nonce cookie`);
export function decodeAuthContextCookie(cookieValue: string): AuthContext | null {
logger.silly(`decoding StaticWebAppsAuthContextCookie cookie`);
const cookies = cookie.parse(cookieValue);
if (cookies[NONCE]) {
const decodedValue = Buffer.from(cookies[NONCE], "base64").toString();
logger.silly(` - Nonce: ${chalk.yellow(decodedValue)}`);
return decodedValue;
if (cookies[SWA_AUTH_CONTEXT_COOKIE]) {
const decodedValue = Buffer.from(cookies[SWA_AUTH_CONTEXT_COOKIE], "base64").toString();
logger.silly(` - StaticWebAppsAuthContextCookie: ${chalk.yellow(decodedValue)}`);
return JSON.parse(decodedValue);
}
logger.silly(` - no cookie 'Nonce' found`);
logger.silly(` - no cookie 'StaticWebAppsAuthContextCookie' found`);
return null;
}

Просмотреть файл

@ -1,4 +1,4 @@
import { decodeNonceCookie, parseUrl, response, validateNonceCookie } from "../../../core";
import { decodeAuthContextCookie, parseUrl, response, validateAuthContextCookie } from "../../../core";
import * as http from "http";
import * as https from "https";
import * as querystring from "querystring";
@ -374,7 +374,7 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess
const { cookie } = request.headers;
if (!cookie || !validateNonceCookie(cookie)) {
if (!cookie || !validateAuthContextCookie(cookie)) {
context.res = response({
context,
status: 401,
@ -389,9 +389,9 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess
const codeValue = url.searchParams.get("code");
const stateValue = url.searchParams.get("state");
const nonce = decodeNonceCookie(cookie);
const authContext = decodeAuthContextCookie(cookie);
if (!nonce || hashStateGuid(nonce) !== stateValue) {
if (!authContext?.authNonce || hashStateGuid(authContext.authNonce) !== stateValue) {
context.res = response({
context,
status: 401,
@ -401,7 +401,7 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess
return;
}
if (isNonceExpired(nonce)) {
if (isNonceExpired(authContext.authNonce)) {
context.res = response({
context,
status: 401,
@ -473,7 +473,7 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess
context,
cookies: [
{
name: "Nonce",
name: "StaticWebAppsAuthContextCookie",
value: "deleted",
path: "/",
secure: true,
@ -493,7 +493,7 @@ const httpTrigger = async function (context: Context, request: http.IncomingMess
status: 302,
headers: {
status: 302,
Location: `${SWA_CLI_APP_PROTOCOL}://${DEFAULT_CONFIG.host}:${DEFAULT_CONFIG.port}`,
Location: authContext.postLoginRedirectUri ?? "/",
},
body: "",
});

Просмотреть файл

@ -2,9 +2,9 @@ import { response } from "../../../core";
import * as http from "http";
import { SWA_CLI_APP_PROTOCOL } from "../../../core/constants";
import { DEFAULT_CONFIG } from "../../../config";
import { hashStateGuid, newNonceWithExpiration } from "../../../core/utils/auth";
import { extractPostLoginRedirectUri, hashStateGuid, newNonceWithExpiration } from "../../../core/utils/auth";
const httpTrigger = async function (context: Context, _request: http.IncomingMessage, customAuth?: SWAConfigFileAuth) {
const httpTrigger = async function (context: Context, request: http.IncomingMessage, customAuth?: SWAConfigFileAuth) {
await Promise.resolve();
const providerName = context.bindingData?.provider?.toLowerCase() || "";
@ -44,6 +44,12 @@ const httpTrigger = async function (context: Context, _request: http.IncomingMes
}
const state = newNonceWithExpiration();
const authContext: AuthContext = {
authNonce: state,
postLoginRedirectUri: extractPostLoginRedirectUri(SWA_CLI_APP_PROTOCOL, request.headers.host, request.url),
};
const hashedState = hashStateGuid(state);
const redirectUri = `${SWA_CLI_APP_PROTOCOL}://${DEFAULT_CONFIG.host}:${DEFAULT_CONFIG.port}`;
@ -56,8 +62,8 @@ const httpTrigger = async function (context: Context, _request: http.IncomingMes
context,
cookies: [
{
name: "Nonce",
value: btoa(state),
name: "StaticWebAppsAuthContextCookie",
value: btoa(JSON.stringify(authContext)),
domain: DEFAULT_CONFIG.host,
path: "/",
secure: true,

6
src/swa.d.ts поставляемый
Просмотреть файл

@ -240,6 +240,12 @@ declare type SWACLIConfigInfo = {
declare type ResponseOptions = {
[key: string]: any;
};
declare type AuthContext = {
authNonce: string;
postLoginRedirectUri?: string;
};
declare type ClientPrincipal = {
identityProvider: string;
userId: string;