mirror of
1
Fork 0

login flow up till app registration

This commit is contained in:
f0x 2022-09-10 19:23:50 +02:00
parent cc69e65a72
commit a4bb869d0f
10 changed files with 238 additions and 20 deletions

View File

@ -44,6 +44,9 @@ $blue1: #3a9fde; /* darker blue for smaller elements (borders), can only be used
$blue2: #66befe; /* all-round accent color, can be used with $gray1 (6.8), $gray2 (5.5), $gray3 (4.9), $gray4 (4.5) */ $blue2: #66befe; /* all-round accent color, can be used with $gray1 (6.8), $gray2 (5.5), $gray3 (4.9), $gray4 (4.5) */
$blue3: #89caff; /* hover/selected accent to $blue2, can be used with $gray1 (7.9), $gray2 (6.3), $gray3 (5.6), $gray4 (5.2), $gray5 (4.7) */ $blue3: #89caff; /* hover/selected accent to $blue2, can be used with $gray1 (7.9), $gray2 (6.3), $gray3 (5.6), $gray4 (5.2), $gray5 (4.7) */
$error1: #860000; /* Error border/foreground text, can be used with $error2 (5.0), $white1 (10), $white2 (5.1) */
$error2: #ff9796; /* Error background text, can be used with $error1 (5.0), $gray1 (6.6), $gray2 (5.3), $gray3 (4.8) */
$fg: $white1; $fg: $white1;
$bg: $gray1; $bg: $gray1;

View File

@ -38,7 +38,9 @@
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"reactify": "^1.1.1", "reactify": "^1.1.1",
"redux-devtools-extension": "^2.13.9",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1",
"uglifyify": "^5.0.2", "uglifyify": "^5.0.2",
"wouter": "^2.8.0-alpha.2" "wouter": "^2.8.0-alpha.2"
}, },

View File

@ -18,6 +18,76 @@
"use strict"; "use strict";
const Promise = require("bluebird");
const React = require("react");
const Redux = require("react-redux");
const { setInstance } = require("../redux/reducers/instances").actions;
const { updateInstance, updateRegistration } = require("../lib/api");
module.exports = function Login() { module.exports = function Login() {
return (null); const dispatch = Redux.useDispatch();
const [ instanceField, setInstanceField ] = React.useState("");
const [ errorMsg, setErrorMsg ] = React.useState();
const instanceFieldRef = React.useRef("");
React.useEffect(() => {
// check if current domain runs an instance
Promise.try(() => {
console.log("trying", window.location.origin);
return dispatch(updateInstance(window.location.origin));
}).then((json) => {
if (instanceFieldRef.current.length == 0) { // user hasn't started typing yet
dispatch(setInstance(json.uri));
instanceFieldRef.current = json.uri;
setInstanceField(json.uri);
}
}).catch((e) => {
console.log("Current domain does not host a valid instance: ", e);
});
}, []);
function tryInstance() {
Promise.try(() => {
return dispatch(updateInstance(instanceFieldRef.current)).catch((e) => {
// TODO: clearer error messages for common errors
console.log(e);
throw e;
});
}).then((instance) => {
// return dispatch(updateRegistration);
}).catch((e) => {
setErrorMsg(
<>
<b>{e.type}</b>
<span>{e.message}</span>
</>
);
});
}
function updateInstanceField(e) {
if (e.key == "Enter") {
tryInstance(instanceField);
} else {
setInstanceField(e.target.value);
instanceFieldRef.current = e.target.value;
}
}
return (
<section className="login">
<h1>OAUTH Login:</h1>
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor="instance">Instance: </label>
<input value={instanceField} onChange={updateInstanceField} id="instance"/>
{errorMsg &&
<div className="error">
{errorMsg}
</div>
}
<button onClick={tryInstance}>Authenticate</button>
</form>
</section>
);
}; };

View File

@ -33,7 +33,7 @@ const ErrorFallback = require("./components/error");
const oauthLib = require("./lib/oauth"); const oauthLib = require("./lib/oauth");
// require("./style.css"); require("./style.css");
// TODO: nested categories? // TODO: nested categories?
const nav = { const nav = {
@ -90,7 +90,7 @@ function App() {
{sidebar} {sidebar}
{/* <button className="logout" onClick={oauth.logout}>Log out</button> */} {/* <button className="logout" onClick={oauth.logout}>Log out</button> */}
</div> </div>
<section> <section className="with-sidebar">
<Switch> <Switch>
{panelRouter} {panelRouter}
</Switch> </Switch>
@ -98,7 +98,9 @@ function App() {
</> </>
); );
} else { } else {
return <Login />; return (
<Login />
);
} }
} }

View File

@ -0,0 +1,94 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const Promise = require("bluebird");
const { setRegistration } = require("../redux/reducers/oauth").actions;
const { setInstanceInfo } = require("../redux/reducers/instances").actions;
function apiCall(base, method, route, {payload, headers={}}) {
return Promise.try(() => {
let url = new URL(base);
url.pathname = route;
let body;
if (payload != undefined) {
body = JSON.stringify(payload);
}
let fetchHeaders = {
"Content-Type": "application/json",
...headers
};
return fetch(url.toString(), {
method: method,
headers: fetchHeaders,
body: body
});
}).then((res) => {
if (res.status == 200) {
return res.json();
} else {
throw res;
}
});
}
function getCurrentUrl() {
return `${window.location.origin}${window.location.pathname}`;
}
function updateInstance(domain) {
return function(dispatch, getState) {
/* check if domain is valid instance, then register client if needed */
return Promise.try(() => {
return apiCall(domain, "GET", "/api/v1/instance", {
headers: {
"Content-Type": "text/plain"
}
});
}).then((json) => {
if (json && json.uri) { // TODO: validate instance json more?
dispatch(setInstanceInfo(json.uri, json));
return json;
}
});
};
}
function updateRegistration() {
return function(dispatch, getState) {
let base = getState().oauth.instance;
return Promise.try(() => {
return apiCall(base, "POST", "/api/v1/apps", {
client_name: "GoToSocial Settings",
scopes: "write admin",
redirect_uris: getCurrentUrl(),
website: getCurrentUrl()
});
}).then((json) => {
console.log(json);
dispatch(setRegistration(base, json));
});
};
}
module.exports = { updateInstance, updateRegistration };

View File

@ -18,22 +18,27 @@
"use strict"; "use strict";
const { createStore, combineReducers } = require("redux"); const { createStore, combineReducers, applyMiddleware } = require("redux");
const { persistStore, persistReducer } = require("redux-persist"); const { persistStore, persistReducer } = require("redux-persist");
const thunk = require("redux-thunk").default;
const { composeWithDevTools } = require("redux-devtools-extension");
const persistConfig = { const persistConfig = {
key: "gotosocial-settings", key: "gotosocial-settings",
storage: require("redux-persist/lib/storage").default, storage: require("redux-persist/lib/storage").default,
stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel2").default stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel2").default,
whitelist: ['oauth']
}; };
const combinedReducers = combineReducers({ const combinedReducers = combineReducers({
oauth: require("./reducers/oauth").reducer oauth: require("./reducers/oauth").reducer,
instances: require("./reducers/instances").reducer,
}); });
const persistedReducer = persistReducer(persistConfig, combinedReducers); const persistedReducer = persistReducer(persistConfig, combinedReducers);
const composedEnhancer = composeWithDevTools(applyMiddleware(thunk));
const store = createStore(persistedReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); const store = createStore(persistedReducer, composedEnhancer);
const persistor = persistStore(store); const persistor = persistStore(store);
module.exports = { store, persistor }; module.exports = { store, persistor };

View File

@ -0,0 +1,38 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const {createSlice} = require("@reduxjs/toolkit");
module.exports = createSlice({
name: "instances",
initialState: {
info: {},
current: undefined
},
reducers: {
setInstance: (state, {payload}) => {
state.current = payload;
},
setInstanceInfo: (state, {payload}) => {
let [key, info] = payload;
state.info[key] = info;
},
}
});

View File

@ -23,20 +23,13 @@ const {createSlice} = require("@reduxjs/toolkit");
module.exports = createSlice({ module.exports = createSlice({
name: "oauth", name: "oauth",
initialState: { initialState: {
loggedIn: false loggedIn: false,
registrations: {}
}, },
reducers: { reducers: {
setInstance: (state, {payload}) => {
return {
...state,
instance: payload
};
},
setRegistration: (state, {payload}) => { setRegistration: (state, {payload}) => {
return { let [key, info] = payload;
...state, state.instanceRegistration[key] = info;
}
} }
} }
}); });

View File

@ -32,7 +32,7 @@ section {
display: grid; display: grid;
grid-template-columns: 1fr min(92%, 90ch) 1fr; grid-template-columns: 1fr min(92%, 90ch) 1fr;
section { section.with-sidebar {
border-left: none; border-left: none;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
@ -128,10 +128,16 @@ input, select, textarea {
} }
.error { .error {
background: $error2;
border: 1px solid $error1;
border-radius: $br;
color: $error1;
font-weight: bold; font-weight: bold;
padding: 0.5rem;
pre { pre {
background: $bg; background: $bg;
color: $fg;
padding: 1rem; padding: 1rem;
overflow: auto; overflow: auto;
margin: 0; margin: 0;

View File

@ -4854,6 +4854,11 @@ recast@^0.11.17:
private "~0.1.5" private "~0.1.5"
source-map "~0.5.0" source-map "~0.5.0"
redux-devtools-extension@^2.13.9:
version "2.13.9"
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7"
integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==
redux-persist@^6.0.0: redux-persist@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"