Skip to content

Commit

Permalink
feat: add PKCE
Browse files Browse the repository at this point in the history
  • Loading branch information
avdb13 committed Nov 24, 2024
1 parent c75fe0b commit f1cafbf
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface ProviderTextFieldProps extends ProviderFieldProps {

type ProviderBooleanProperties =
| "enabled"
| "use_pkce"
| "account_linking_enabled"
| "auto_verify_email";

Expand Down Expand Up @@ -337,6 +338,18 @@ export default class CreateOrEditOAuthProviderModal extends Component<
handleBooleanPropertyChange,
)}
/>
<ProviderCheckboxField
id="use-pkce"
i18nKey="use_pkce"
checked={provider?.use_pkce}
onInput={linkEvent(
{
modal: this,
property: "use_pkce",
},
handleBooleanPropertyChange,
)}
/>
<ProviderCheckboxField
id="oauth-enabled"
i18nKey="oauth_enabled"
Expand Down
36 changes: 27 additions & 9 deletions src/shared/components/home/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { UnreadCounterService } from "../../services";
import { RouteData } from "../../interfaces";
import { IRoutePropsWithFetch } from "../../routes";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import {
generateCodeVerifier,
createCodeChallenge,
} from "@utils/helpers/oauth";

interface LoginProps {
prev?: string;
Expand Down Expand Up @@ -126,16 +130,27 @@ export async function handleUseOAuthProvider(params: {
const redirectUri = `${window.location.origin}/oauth/callback`;

const state = crypto.randomUUID();

const codeVerifier = params.oauth_provider.use_pkce && generateCodeVerifier();
const codeChallenge =
codeVerifier && (await createCodeChallenge(codeVerifier));

const queryPairs = [
`client_id=${encodeURIComponent(params.oauth_provider.client_id)}`,
`response_type=code`,
`scope=${encodeURIComponent(params.oauth_provider.scopes)}`,
`redirect_uri=${encodeURIComponent(redirectUri)}`,
`state=${state}`,
...(params.oauth_provider.use_pkce
? [
`code_challenge=${encodeURIComponent(codeChallenge)}`,
"code_challenge_method=S256",
]
: []),
];

const requestUri =
params.oauth_provider.authorization_endpoint +
"?" +
[
`client_id=${encodeURIComponent(params.oauth_provider.client_id)}`,
`response_type=code`,
`scope=${encodeURIComponent(params.oauth_provider.scopes)}`,
`redirect_uri=${encodeURIComponent(redirectUri)}`,
`state=${state}`,
].join("&");
params.oauth_provider.authorization_endpoint + "?" + queryPairs.join("&");

// store state in local storage
localStorage.setItem(
Expand All @@ -149,6 +164,9 @@ export async function handleUseOAuthProvider(params: {
answer: params.answer,
show_nsfw: params.show_nsfw,
expires_at: Date.now() + 5 * 60_000,
...(params.oauth_provider.use_pkce
? { pkce_code_verifier: codeVerifier }
: {}),
}),
);

Expand Down
3 changes: 3 additions & 0 deletions src/shared/components/home/oauth/oauth-callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export class OAuthCallback extends Component<OAuthCallbackRouteProps, State> {
show_nsfw: local_oauth_state.show_nsfw,
username: local_oauth_state.username,
answer: local_oauth_state.answer,
...(local_oauth_state?.pkce_code_verifier && {
pkce_code_verifier: local_oauth_state.pkce_code_verifier,
}),
});

switch (loginRes.state) {
Expand Down
4 changes: 4 additions & 0 deletions src/shared/components/home/oauth/oauth-provider-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default function OAuthProviderListItem({
i18nKey="oauth_account_linking_enabled"
data={boolToYesNo(provider.account_linking_enabled)}
/>
<TextInfoField
i18nKey="use_pkce"
data={boolToYesNo(provider.use_pkce)}
/>
<TextInfoField
i18nKey="oauth_enabled"
data={boolToYesNo(provider.enabled)}
Expand Down
1 change: 1 addition & 0 deletions src/shared/components/home/oauth/oauth-providers-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const PRESET_OAUTH_PROVIDERS: ProviderToEdit[] = [
scopes: "openid email",
auto_verify_email: true,
account_linking_enabled: true,
use_pkce: true,
enabled: true,
},
// additional preset providers can be added here
Expand Down
19 changes: 19 additions & 0 deletions src/shared/utils/helpers/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function base64URLEncode(buffer: Uint8Array | ArrayBuffer) {
return btoa(String.fromCharCode.apply(null, buffer))
.replace(/\//g, "_")
.replace(/\+/g, "-")
.replace(/=\+$/, "");
}

export function generateCodeVerifier(length: number = 64) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return base64URLEncode(array);
}

export async function createCodeChallenge(codeVerifier: string) {
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await window.crypto.subtle.digest("SHA-256", data);
return base64URLEncode(digest);
}

0 comments on commit f1cafbf

Please sign in to comment.