Skip to content

Commit

Permalink
add PKCE
Browse files Browse the repository at this point in the history
  • Loading branch information
avdb13 committed Nov 12, 2024
1 parent f9b1096 commit 5de4ba2
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface ProviderTextFieldProps extends ProviderFieldProps {
}

type ProviderBooleanProperties =
| "use_pkce"
| "enabled"
| "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 ?? false}
onInput={linkEvent(
{
modal: this,
property: "use_pkce",
},
handleBooleanPropertyChange,
)}
/>
<ProviderCheckboxField
id="oauth-enabled"
i18nKey="oauth_enabled"
Expand Down
25 changes: 18 additions & 7 deletions src/shared/components/home/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { UnreadCounterService } from "../../services";
import { RouteData } from "../../interfaces";
import { IRoutePropsWithFetch } from "../../routes";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import { generatePKCE } from "@utils/helpers/oauth";

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

const state = crypto.randomUUID();
const [code_challenge, code_verifier] = params.oauth_provider.use_pkce
? await generatePKCE()
: [undefined, undefined];

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}`,
];

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("&");
queryPairs.join("&") +
(code_challenge && code_verifier
? `&code_challenge=${encodeURIComponent(code_challenge)}&code_challenge_method=S256`
: ``);

// store state in local storage
localStorage.setItem(
"oauth_state",
JSON.stringify({
state,
pkce_code_verifier: code_verifier,
oauth_provider_id: params.oauth_provider.id,
redirect_uri: redirectUri,
prev: params.prev ?? "/",
Expand Down
1 change: 1 addition & 0 deletions src/shared/components/home/oauth/oauth-callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class OAuthCallback extends Component<OAuthCallbackRouteProps, State> {
} else {
const loginRes = await HttpService.client.authenticateWithOAuth({
code: this.props.code,
pkce_code_verifier: local_oauth_state?.pkce_code_verifier,
oauth_provider_id: local_oauth_state.oauth_provider_id,
redirect_uri: local_oauth_state.redirect_uri,
show_nsfw: local_oauth_state.show_nsfw,
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: false,
enabled: true,
},
// additional preset providers can be added here
Expand Down
44 changes: 44 additions & 0 deletions src/shared/utils/helpers/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const PKCE_VERIFIER_LENGTH = 96;

const PKCE_ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";

const PKCE_ALGORITHM = "SHA-256";

function urlUnpaddedBase64Encode(value: string): string {
return btoa(
String.fromCharCode.apply(
null,
new Uint8Array(new TextEncoder().encode(value)),
),
)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}

export async function generatePKCE(): Promise<[string, string]> {
const randomValues = crypto.getRandomValues(
new Uint32Array(PKCE_VERIFIER_LENGTH),
);

const code_verifier = urlUnpaddedBase64Encode(
Array.from(randomValues)
.map(n => PKCE_ALPHABET[n % PKCE_ALPHABET.length])
.join(""),
);
const code_verifier_digest = await crypto.subtle.digest(
PKCE_ALGORITHM,
new TextEncoder().encode(code_verifier),
);
const code_verifier_hash = new Uint8Array(code_verifier_digest);

let code_challenge = "";
for (let i = 0; i < code_verifier_hash.byteLength; i++) {
code_challenge = code_challenge.concat(
String.fromCharCode(code_verifier_hash[i]),
);
}

return [urlUnpaddedBase64Encode(code_challenge), code_verifier];
}

0 comments on commit 5de4ba2

Please sign in to comment.