mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-25 10:45:41 +03:00
OAuth PKCE is in.
Reference PR: https://github.com/mastodon/mastodon/pull/31129
This commit is contained in:
parent
2e6074d794
commit
7c56b64e8a
5 changed files with 138 additions and 28 deletions
12
src/app.jsx
12
src/app.jsx
|
@ -324,6 +324,7 @@ function App() {
|
|||
const clientID = store.sessionCookie.get('clientID');
|
||||
const clientSecret = store.sessionCookie.get('clientSecret');
|
||||
const vapidKey = store.sessionCookie.get('vapidKey');
|
||||
const verifier = store.sessionCookie.get('codeVerifier');
|
||||
|
||||
(async () => {
|
||||
setUIState('loading');
|
||||
|
@ -332,8 +333,10 @@ function App() {
|
|||
client_id: clientID,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
code_verifier: verifier || undefined,
|
||||
});
|
||||
|
||||
if (accessToken) {
|
||||
const client = initClient({ instance: instanceURL, accessToken });
|
||||
await Promise.allSettled([
|
||||
initPreferences(client),
|
||||
|
@ -341,9 +344,13 @@ function App() {
|
|||
initAccount(client, instanceURL, accessToken, vapidKey),
|
||||
]);
|
||||
initStates();
|
||||
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
|
||||
|
||||
setIsLoggedIn(true);
|
||||
setUIState('default');
|
||||
} else {
|
||||
setUIState('error');
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
|
||||
|
@ -387,6 +394,11 @@ function App() {
|
|||
setUIState('default');
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
store.sessionCookie.del('clientID');
|
||||
store.sessionCookie.del('clientSecret');
|
||||
store.sessionCookie.del('codeVerifier');
|
||||
}, []);
|
||||
|
||||
let location = useLocation();
|
||||
|
|
|
@ -1337,7 +1337,7 @@ msgid "Accounts…"
|
|||
msgstr ""
|
||||
|
||||
#: src/components/nav-menu.jsx:363
|
||||
#: src/pages/login.jsx:142
|
||||
#: src/pages/login.jsx:166
|
||||
#: src/pages/status.jsx:792
|
||||
#: src/pages/welcome.jsx:64
|
||||
msgid "Log in"
|
||||
|
@ -1733,7 +1733,7 @@ msgstr ""
|
|||
#: src/components/shortcuts-settings.jsx:75
|
||||
#: src/components/shortcuts-settings.jsx:84
|
||||
#: src/components/shortcuts-settings.jsx:122
|
||||
#: src/pages/login.jsx:146
|
||||
#: src/pages/login.jsx:170
|
||||
msgid "Instance"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2353,7 +2353,7 @@ msgstr "Login required."
|
|||
|
||||
#: src/compose.jsx:90
|
||||
#: src/pages/http-route.jsx:91
|
||||
#: src/pages/login.jsx:223
|
||||
#: src/pages/login.jsx:247
|
||||
msgid "Go home"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3025,23 +3025,28 @@ msgstr ""
|
|||
msgid "No lists yet."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login.jsx:185
|
||||
#: src/pages/login.jsx:86
|
||||
#: src/pages/login.jsx:99
|
||||
msgid "Failed to register application"
|
||||
msgstr "Failed to register application"
|
||||
|
||||
#: src/pages/login.jsx:209
|
||||
msgid "e.g. “mastodon.social”"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login.jsx:196
|
||||
#: src/pages/login.jsx:220
|
||||
msgid "Failed to log in. Please try again or try another instance."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login.jsx:208
|
||||
#: src/pages/login.jsx:232
|
||||
msgid "Continue with {selectedInstanceText}"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login.jsx:209
|
||||
#: src/pages/login.jsx:233
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login.jsx:217
|
||||
#: src/pages/login.jsx:241
|
||||
msgid "Don't have an account? Create one!"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -11,7 +11,12 @@ import LangSelector from '../components/lang-selector';
|
|||
import Link from '../components/link';
|
||||
import Loader from '../components/loader';
|
||||
import instancesListURL from '../data/instances.json?url';
|
||||
import { getAuthorizationURL, registerApplication } from '../utils/auth';
|
||||
import {
|
||||
getAuthorizationURL,
|
||||
getPKCEAuthorizationURL,
|
||||
registerApplication,
|
||||
} from '../utils/auth';
|
||||
import { supportsPKCE } from '../utils/oauth-pkce';
|
||||
import store from '../utils/store';
|
||||
import useTitle from '../utils/useTitle';
|
||||
|
||||
|
@ -63,6 +68,24 @@ function Login() {
|
|||
instanceURL,
|
||||
});
|
||||
|
||||
const authPKCE = await supportsPKCE({ instanceURL });
|
||||
console.log({ authPKCE });
|
||||
if (authPKCE) {
|
||||
if (client_id && client_secret) {
|
||||
store.sessionCookie.set('clientID', client_id);
|
||||
store.sessionCookie.set('clientSecret', client_secret);
|
||||
store.sessionCookie.set('vapidKey', vapid_key);
|
||||
|
||||
const [url, verifier] = await getPKCEAuthorizationURL({
|
||||
instanceURL,
|
||||
client_id,
|
||||
});
|
||||
store.sessionCookie.set('codeVerifier', verifier);
|
||||
location.href = url;
|
||||
} else {
|
||||
alert(t`Failed to register application`);
|
||||
}
|
||||
} else {
|
||||
if (client_id && client_secret) {
|
||||
store.sessionCookie.set('clientID', client_id);
|
||||
store.sessionCookie.set('clientSecret', client_secret);
|
||||
|
@ -73,7 +96,8 @@ function Login() {
|
|||
client_id,
|
||||
});
|
||||
} else {
|
||||
alert('Failed to register application');
|
||||
alert(t`Failed to register application`);
|
||||
}
|
||||
}
|
||||
setUIState('default');
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { generateCodeChallenge, verifier } from './oauth-pkce';
|
||||
|
||||
const { PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_WEBSITE: WEBSITE } = import.meta
|
||||
.env;
|
||||
|
||||
|
@ -25,6 +27,21 @@ export async function registerApplication({ instanceURL }) {
|
|||
return registrationJSON;
|
||||
}
|
||||
|
||||
export async function getPKCEAuthorizationURL({ instanceURL, client_id }) {
|
||||
const codeVerifier = verifier();
|
||||
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||
const params = new URLSearchParams({
|
||||
client_id,
|
||||
code_challenge_method: 'S256',
|
||||
code_challenge: codeChallenge,
|
||||
redirect_uri: location.origin + location.pathname,
|
||||
response_type: 'code',
|
||||
scope: SCOPES,
|
||||
});
|
||||
const authorizationURL = `https://${instanceURL}/oauth/authorize?${params.toString()}`;
|
||||
return [authorizationURL, codeVerifier];
|
||||
}
|
||||
|
||||
export async function getAuthorizationURL({ instanceURL, client_id }) {
|
||||
const authorizationParams = new URLSearchParams({
|
||||
client_id,
|
||||
|
@ -42,15 +59,23 @@ export async function getAccessToken({
|
|||
client_id,
|
||||
client_secret,
|
||||
code,
|
||||
code_verifier,
|
||||
}) {
|
||||
const params = new URLSearchParams({
|
||||
client_id,
|
||||
client_secret,
|
||||
redirect_uri: location.origin + location.pathname,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
scope: SCOPES,
|
||||
// client_secret,
|
||||
// code_verifier,
|
||||
});
|
||||
if (client_secret) {
|
||||
params.append('client_secret', client_secret);
|
||||
}
|
||||
if (code_verifier) {
|
||||
params.append('code_verifier', code_verifier);
|
||||
}
|
||||
const tokenResponse = await fetch(`https://${instanceURL}/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
44
src/utils/oauth-pkce.js
Normal file
44
src/utils/oauth-pkce.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
function dec2hex(dec) {
|
||||
return ('0' + dec.toString(16)).slice(-2);
|
||||
}
|
||||
export function verifier() {
|
||||
var array = new Uint32Array(56 / 2);
|
||||
window.crypto.getRandomValues(array);
|
||||
return Array.from(array, dec2hex).join('');
|
||||
}
|
||||
function sha256(plain) {
|
||||
// returns promise ArrayBuffer
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(plain);
|
||||
return window.crypto.subtle.digest('SHA-256', data);
|
||||
}
|
||||
function base64urlencode(a) {
|
||||
let str = '';
|
||||
const bytes = new Uint8Array(a);
|
||||
const len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
str += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
export async function generateCodeChallenge(v) {
|
||||
const hashed = await sha256(v);
|
||||
return base64urlencode(hashed);
|
||||
}
|
||||
|
||||
// If https://mastodon.social/.well-known/oauth-authorization-server exists, means support PKCE
|
||||
export async function supportsPKCE({ instanceURL }) {
|
||||
if (!instanceURL) return false;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://${instanceURL}/.well-known/oauth-authorization-server`,
|
||||
);
|
||||
if (!res.ok || res.status !== 200) return false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging
|
||||
window.__generateCodeChallenge = generateCodeChallenge;
|
Loading…
Reference in a new issue