mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-22 19:01:26 +00:00
feat: add PKCE
This commit is contained in:
parent
c75fe0b0dc
commit
bb3ab9b903
7 changed files with 71 additions and 10 deletions
|
@ -60,7 +60,7 @@
|
|||
"inferno-router": "^8.2.3",
|
||||
"inferno-server": "^8.2.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lemmy-js-client": "0.20.0-alpha.17",
|
||||
"lemmy-js-client": "0.20.0-pkce.1",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-bidi": "^0.2.0",
|
||||
|
|
|
@ -51,6 +51,7 @@ interface ProviderTextFieldProps extends ProviderFieldProps {
|
|||
|
||||
type ProviderBooleanProperties =
|
||||
| "enabled"
|
||||
| "use_pkce"
|
||||
| "account_linking_enabled"
|
||||
| "auto_verify_email";
|
||||
|
||||
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
@ -126,16 +130,32 @@ export async function handleUseOAuthProvider(params: {
|
|||
const redirectUri = `${window.location.origin}/oauth/callback`;
|
||||
|
||||
const state = crypto.randomUUID();
|
||||
|
||||
let codeVerifier: string | undefined;
|
||||
if (params.oauth_provider.use_pkce) {
|
||||
codeVerifier = generateCodeVerifier();
|
||||
}
|
||||
let codeChallenge: string | undefined;
|
||||
if (codeVerifier) {
|
||||
codeChallenge = 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}`,
|
||||
...(codeChallenge
|
||||
? [
|
||||
`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(
|
||||
|
@ -149,6 +169,7 @@ export async function handleUseOAuthProvider(params: {
|
|||
answer: params.answer,
|
||||
show_nsfw: params.show_nsfw,
|
||||
expires_at: Date.now() + 5 * 60_000,
|
||||
...(codeVerifier ? { pkce_code_verifier: codeVerifier } : {}),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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
|
||||
|
|
19
src/shared/utils/helpers/oauth.ts
Normal file
19
src/shared/utils/helpers/oauth.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export function base64URLEncode(buffer: Uint8Array | ArrayBuffer) {
|
||||
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))
|
||||
.replace(/\//g, "_")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
Loading…
Reference in a new issue