Merge pull request #889 from Timothyw0/main
[Feature] Add Twitter/X Custom Auth Support
This commit is contained in:
Коммит
6745391a30
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче