add bitbucket oauth (#223)

* add bitbucket oauth

* updated button style

* fixed docs

---------

Co-authored-by: Zai Shi <zaishi00@outlook.com>
This commit is contained in:
Manoj Kumar 2024-09-06 03:50:38 +05:30 committed by GitHub
parent f60508031e
commit bba2b1884d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 107 additions and 31 deletions

View File

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "StandardOAuthProviderType" ADD VALUE 'BITBUCKET';

View File

@ -581,6 +581,7 @@ enum StandardOAuthProviderType {
SPOTIFY
DISCORD
GITLAB
BITBUCKET
}
//#endregion

View File

@ -12,6 +12,7 @@ import { SpotifyProvider } from "./providers/spotify";
import { MockProvider } from "./providers/mock";
import { DiscordProvider } from "@/oauth/providers/discord";
import { GitlabProvider } from "./providers/gitlab";
import { BitbucketProvider } from "./providers/bitbucket";
const _providers = {
github: GithubProvider,
@ -21,6 +22,7 @@ const _providers = {
spotify: SpotifyProvider,
discord: DiscordProvider,
gitlab: GitlabProvider,
bitbucket: BitbucketProvider,
} as const;
const mockProvider = MockProvider;

View File

@ -0,0 +1,48 @@
import { OAuthBaseProvider, TokenSet } from "./base";
import { OAuthUserInfo, validateUserInfo } from "../utils";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
export class BitbucketProvider extends OAuthBaseProvider {
private constructor(
...args: ConstructorParameters<typeof OAuthBaseProvider>
) {
super(...args);
}
static async create(options: { clientId: string, clientSecret: string }) {
return new BitbucketProvider(
...(await OAuthBaseProvider.createConstructorArgs({
issuer: "https://bitbucket.org",
authorizationEndpoint: "https://bitbucket.org/site/oauth2/authorize",
tokenEndpoint: "https://bitbucket.org/site/oauth2/access_token",
redirectUri:
getEnvVariable("STACK_BASE_URL") +
"/api/v1/auth/oauth/callback/bitbucket",
baseScope: "account email",
...options,
}))
);
}
async postProcessUserInfo(tokenSet: TokenSet): Promise<OAuthUserInfo> {
const headers = {
Authorization: `Bearer ${tokenSet.accessToken}`,
};
const [userInfo, emailData] = await Promise.all([
fetch("https://api.bitbucket.org/2.0/user", { headers }).then((res) =>
res.json()
),
fetch("https://api.bitbucket.org/2.0/user/emails", { headers }).then(
(res) => res.json()
),
]);
return validateUserInfo({
accountId: userInfo.account_id,
displayName: userInfo.display_name,
email: emailData?.values[0].email,
profileImageUrl: userInfo.links.avatar.href,
emailVerified: emailData?.values[0].is_confirmed,
});
}
}

View File

@ -11,7 +11,7 @@ import * as yup from "yup";
export const projectFormSchema = yup.object({
displayName: yup.string().min(1, "Display name is required").required(),
signInMethods: yup.array(yup.string().oneOf(["google", "github", "microsoft", "facebook", "credential", "magicLink", "discord", "gitlab"]).required()).required(),
signInMethods: yup.array(yup.string().oneOf(["google", "github", "microsoft", "facebook", "credential", "magicLink", "discord", "gitlab", "bitbucket"]).required()).required(),
});
export type ProjectFormValues = yup.InferType<typeof projectFormSchema>

View File

@ -24,6 +24,7 @@ function toTitle(id: string) {
spotify: "Spotify",
discord: "Discord",
gitlab: "GitLab",
bitbucket: "Bitbucket",
}[id];
}

View File

@ -10,6 +10,7 @@ const mockedProviders = [
"spotify",
"discord",
"gitlab",
"bitbucket",
];
const configuration: Configuration = {

View File

@ -53,6 +53,11 @@ To use your own OAuth provider setups in production, follow these steps for each
Callback URL:
`https://api.stack-auth.com/api/v1/auth/oauth/callback/gitlab`
</Tab>
<Tab title="Bitbucket">
[Bitbucket OAuth Setup Guide](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud)
Callback URL:
`https://api.stack-auth.com/api/v1/auth/oauth/callback/bitbucket`
</Tab>
</Tabs>
2. **Enter OAuth Credentials**: Go to the `Auth Methods` section in the Stack dashboard, open the provider's settings, switch from shared keys to custom keys, and enter the client ID and client secret.

View File

@ -1,7 +1,7 @@
export const standardProviders = ["google", "github", "facebook", "microsoft", "spotify", "discord", "gitlab"] as const;
export const standardProviders = ["google", "github", "facebook", "microsoft", "spotify", "discord", "gitlab", "bitbucket"] as const;
// No more shared providers should be added except for special cases
export const sharedProviders = ["google", "github", "facebook", "microsoft", "spotify"] as const;
export const allProviders = ["google", "github", "facebook", "microsoft", "spotify", "discord", "gitlab"] as const;
export const allProviders = ["google", "github", "facebook", "microsoft", "spotify", "discord", "gitlab", "bitbucket"] as const;
export type ProviderType = typeof allProviders[number];
export type StandardProviderType = typeof standardProviders[number];

View File

@ -72,39 +72,45 @@ function GitlabIcon({ iconSize } : { iconSize: number} ) {
preserveAspectRatio="xMidYMid"
>
<g>
<path
d="M128.07485,236.074667 L128.07485,236.074667 L175.17885,91.1043048 L80.9708495,91.1043048 L128.07485,236.074667 L128.07485,236.074667 Z"
fill="#E24329"
></path>
<path
d="M128.07485,236.074423 L80.9708495,91.104061 L14.9557638,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z"
fill="#FC6D26"
></path>
<path
d="M14.9558857,91.1044267 L14.9558857,91.1044267 L0.641828571,135.159589 C-0.663771429,139.17757 0.766171429,143.57955 4.18438095,146.06275 L128.074971,236.074789 L14.9558857,91.1044267 L14.9558857,91.1044267 Z"
fill="#FCA326"
></path>
<path
d="M14.9558857,91.1045486 L80.9709714,91.1045486 L52.6000762,3.79026286 C51.1408762,-0.703146667 44.7847619,-0.701927619 43.3255619,3.79026286 L14.9558857,91.1045486 L14.9558857,91.1045486 Z"
fill="#E24329"
></path>
<path
d="M128.07485,236.074423 L175.17885,91.104061 L241.193935,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z"
fill="#FC6D26"
></path>
<path
d="M241.193935,91.1044267 L241.193935,91.1044267 L255.507992,135.159589 C256.813592,139.17757 255.38365,143.57955 251.96544,146.06275 L128.07485,236.074789 L241.193935,91.1044267 L241.193935,91.1044267 Z"
fill="#FCA326"
></path>
<path
d="M241.193935,91.1045486 L175.17885,91.1045486 L203.549745,3.79026286 C205.008945,-0.703146667 211.365059,-0.701927619 212.824259,3.79026286 L241.193935,91.1045486 L241.193935,91.1045486 Z"
fill="#E24329"
></path>
<path d="M128.07485,236.074667 L128.07485,236.074667 L175.17885,91.1043048 L80.9708495,91.1043048 L128.07485,236.074667 L128.07485,236.074667 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L80.9708495,91.104061 L14.9557638,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M14.9558857,91.1044267 L14.9558857,91.1044267 L0.641828571,135.159589 C-0.663771429,139.17757 0.766171429,143.57955 4.18438095,146.06275 L128.074971,236.074789 L14.9558857,91.1044267 L14.9558857,91.1044267 Z" fill="#FCA326"></path>
<path d="M14.9558857,91.1045486 L80.9709714,91.1045486 L52.6000762,3.79026286 C51.1408762,-0.703146667 44.7847619,-0.701927619 43.3255619,3.79026286 L14.9558857,91.1045486 L14.9558857,91.1045486 Z" fill="#E24329"></path>
<path d="M128.07485,236.074423 L175.17885,91.104061 L241.193935,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26"></path>
<path d="M241.193935,91.1044267 L241.193935,91.1044267 L255.507992,135.159589 C256.813592,139.17757 255.38365,143.57955 251.96544,146.06275 L128.07485,236.074789 L241.193935,91.1044267 L241.193935,91.1044267 Z" fill="#FCA326"></path>
<path d="M241.193935,91.1045486 L175.17885,91.1045486 L203.549745,3.79026286 C205.008945,-0.703146667 211.365059,-0.701927619 212.824259,3.79026286 L241.193935,91.1045486 L241.193935,91.1045486 Z" fill="#E24329"></path>
</g>
</svg>
);
}
function BitbucketIcon({ iconSize }: { iconSize: number }) {
return (
<svg
preserveAspectRatio="xMidYMid"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.9662264221278978 -0.5824607696358868 257.93281329857973 230.8324730411935"
width={iconSize}
height={iconSize}
>
<linearGradient
id="a"
x1="108.633%"
x2="46.927%"
y1="13.818%"
y2="78.776%"
>
<stop offset=".18" stopColor="#0052cc" />
<stop offset="1" stopColor="#2684ff" />
</linearGradient>
<g fill="none">
<path d="M101.272 152.561h53.449l12.901-75.32H87.06z" />
<path d="M8.308 0A8.202 8.202 0 0 0 .106 9.516l34.819 211.373a11.155 11.155 0 0 0 10.909 9.31h167.04a8.202 8.202 0 0 0 8.201-6.89l34.82-213.752a8.202 8.202 0 0 0-8.203-9.514zm146.616 152.768h-53.315l-14.436-75.42h80.67z" fill="#2684ff"/>
<path d="M244.61 77.242h-76.916l-12.909 75.36h-53.272l-62.902 74.663a11.105 11.105 0 0 0 7.171 2.704H212.73a8.196 8.196 0 0 0 8.196-6.884z" fill="url(#a)"/>
</g>
</svg>
);
}
const changeColor = (c: Color, value: number) => {
if (c.isLight()) {
@ -197,6 +203,16 @@ export function OAuthButton({
};
break;
}
case "bitbucket": {
style = {
backgroundColor: "#fff",
textColor: "#000",
border: "1px solid #ddd",
name: "Bitbucket",
icon: <BitbucketIcon iconSize={iconSize} />,
};
break;
}
default: {
style = {
name: provider,