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

Merge pull request #889 from Timothyw0/main

[Feature] Add Twitter/X Custom Auth Support
This commit is contained in:
Timothy Wang 2024-10-14 10:15:55 -04:00 коммит произвёл GitHub
Родитель 5f440e3068 738b21e961
Коммит 6745391a30
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 47 добавлений и 18 удалений

2
.github/workflows/azuresdkdrop.yml поставляемый
Просмотреть файл

@ -45,7 +45,7 @@ jobs:
- run: npm pack
- name: Upload
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: package
path: "*.tgz"

2
.github/workflows/ci.yml поставляемый
Просмотреть файл

@ -228,7 +228,7 @@ jobs:
- run: npm version prerelease --preid=ci-$GITHUB_RUN_ID --no-git-tag-version
- run: npm pack
- name: Upload
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: static-web-apps-cli
path: "*.tgz"

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

@ -50,7 +50,7 @@ export const SWA_AUTH_COOKIE = `StaticWebAppsAuthCookie`;
export const ALLOWED_HTTP_METHODS_FOR_STATIC_CONTENT = ["GET", "HEAD", "OPTIONS"];
// Custom Auth constants
export const SUPPORTED_CUSTOM_AUTH_PROVIDERS = ["google", "github", "aad", "facebook", "dummy"];
export const SUPPORTED_CUSTOM_AUTH_PROVIDERS = ["google", "github", "aad", "facebook", "twitter", "dummy"];
/*
The full name is required in staticwebapp.config.json's schema that will be normalized to aad
https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad%2Cinvitations
@ -73,6 +73,10 @@ export const CUSTOM_AUTH_TOKEN_ENDPOINT_MAPPING: AuthIdentityTokenEndpoints = {
host: "graph.facebook.com",
path: "/v11.0/oauth/access_token",
},
twitter: {
host: "api.twitter.com",
path: "/2/oauth2/token",
},
};
export const CUSTOM_AUTH_USER_ENDPOINT_MAPPING: AuthIdentityTokenEndpoints = {
google: {
@ -87,18 +91,24 @@ export const CUSTOM_AUTH_USER_ENDPOINT_MAPPING: AuthIdentityTokenEndpoints = {
host: "graph.microsoft.com",
path: "/oidc/userinfo",
},
twitter: {
host: "api.twitter.com",
path: "/2/users/me",
},
};
export const CUSTOM_AUTH_ISS_MAPPING: AuthIdentityIssHosts = {
google: "https://account.google.com",
github: "",
aad: "https://graph.microsoft.com",
facebook: "https://www.facebook.com",
twitter: "https://www.x.com",
};
export const CUSTOM_AUTH_REQUIRED_FIELDS: AuthIdentityRequiredFields = {
google: ["clientIdSettingName", "clientSecretSettingName"],
github: ["clientIdSettingName", "clientSecretSettingName"],
aad: ["clientIdSettingName", "clientSecretSettingName", "openIdIssuer"],
facebook: ["appIdSettingName", "appSecretSettingName"],
twitter: ["consumerKeySettingName", "consumerSecretSettingName"],
};
export const AUTH_STATUS = {

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

@ -12,13 +12,13 @@ function getAuthPaths(isCustomAuth: boolean): Path[] {
paths.push({
method: "GET",
// only match for providers with custom auth support implemented (github, google, aad)
// only match for providers with custom auth support implemented (github, google, aad, facebook, twitter)
route: new RegExp(`^/\\.auth/login/(?<provider>${supportedAuthsRegex})/callback(\\?.*)?$`, "i"),
function: "auth-login-provider-callback",
});
paths.push({
method: "GET",
// only match for providers with custom auth support implemented (github, google, aad)
// only match for providers with custom auth support implemented (github, google, aad, facebook, twitter)
route: new RegExp(`^/\\.auth/login/(?<provider>${supportedAuthsRegex})(\\?.*)?$`, "i"),
function: "auth-login-provider-custom",
});

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

@ -45,14 +45,14 @@ const getAuthClientPrincipal = async function (authProvider: string, codeValue:
}
try {
const user = (await getOAuthUser(authProvider, authToken)) as { [key: string]: string };
const user = (await getOAuthUser(authProvider, authToken)) as Record<string, any>;
const userDetails = user["login"] || user["email"];
const name = user["name"];
const userDetails = user["login"] || user["email"] || user?.data?.["username"];
const name = user["name"] || user?.data?.["name"];
const givenName = user["given_name"];
const familyName = user["family_name"];
const picture = user["picture"];
const userId = user["id"];
const userId = user["id"] || user?.data?.["id"];
const verifiedEmail = user["verified_email"];
const claims: { typ: string; val: string }[] = [
@ -134,7 +134,8 @@ const getAuthClientPrincipal = async function (authProvider: string, codeValue:
claims,
userRoles: ["authenticated", "anonymous"],
};
} catch {
} catch (error) {
console.error(`Error while parsing user information: ${error}`);
return null;
}
};
@ -151,27 +152,42 @@ const getOAuthToken = function (authProvider: string, codeValue: string, authCon
tenantId = authConfigs?.openIdIssuer.split("/")[3];
}
const data = querystring.stringify({
const queryString: Record<string, string> = {
code: codeValue,
client_id: authConfigs?.clientIdSettingName || authConfigs?.appIdSettingName,
client_secret: authConfigs?.clientSecretSettingName || authConfigs?.appSecretSettingName,
grant_type: "authorization_code",
redirect_uri: `${redirectUri}/.auth/login/${authProvider}/callback`,
});
};
if (authProvider !== "twitter") {
queryString.client_id = authConfigs?.clientIdSettingName || authConfigs?.appIdSettingName;
queryString.client_secret = authConfigs?.clientSecretSettingName || authConfigs?.appSecretSettingName;
} else {
queryString.code_verifier = "challenge";
}
const data = querystring.stringify(queryString);
let tokenPath = CUSTOM_AUTH_TOKEN_ENDPOINT_MAPPING?.[authProvider]?.path;
if (authProvider === "aad" && tenantId !== undefined) {
tokenPath = tokenPath.replace("tenantId", tenantId);
}
const headers: Record<string, string | number> = {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(data),
};
if (authProvider === "twitter") {
const keySecretString = `${authConfigs?.consumerKeySettingName}:${authConfigs?.consumerSecretSettingName}`;
const encryptedCredentials = Buffer.from(keySecretString).toString("base64");
headers.Authorization = `Basic ${encryptedCredentials}`;
}
const options = {
host: CUSTOM_AUTH_TOKEN_ENDPOINT_MAPPING?.[authProvider]?.host,
path: tokenPath,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(data),
},
headers: headers,
};
return new Promise((resolve, reject) => {

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

@ -92,6 +92,9 @@ const httpTrigger = async function (context: Context, request: IncomingMessage,
case "facebook":
location = `https://facebook.com/v11.0/dialog/oauth?client_id=${authFields?.appIdSettingName}&redirect_uri=${redirectUri}/.auth/login/facebook/callback&scope=openid&state=${hashedState}&response_type=code`;
break;
case "twitter":
location = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${authFields?.consumerKeySettingName}&redirect_uri=${redirectUri}/.auth/login/twitter/callback&scope=users.read%20tweet.read&state=${hashedState}&code_challenge=challenge&code_challenge_method=plain`;
break;
default:
break;
}