[feature] Application creation + management via API + settings panel (#3906)

* [feature] Application creation + management via API + settings panel

* fix docs links

* add errnorows test

* use known application as shorter

* add comment about side effects
This commit is contained in:
tobi 2025-03-17 15:06:17 +01:00 committed by GitHub
commit d5847e2d2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 3036 additions and 252 deletions

View file

@ -18,33 +18,11 @@
*/
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { OAuthApp, OAuthAccessToken } from "../lib/types/oauth";
/**
* OAuthToken represents a response
* to an OAuth token request.
*/
export interface OAuthToken {
/**
* Most likely to be 'Bearer'
* but may be something else.
*/
token_type: string;
/**
* The actual token. Can be passed in to
* authenticate further requests using the
* Authorization header and the token type.
*/
access_token: string;
}
export interface OAuthApp {
client_id: string;
client_secret: string;
}
export interface OAuthState {
export interface LoginState {
instanceUrl?: string;
loginState: "none" | "callback" | "login" | "logout";
current: "none" | "awaitingcallback" | "loggedin" | "loggedout";
expectingRedirect: boolean;
/**
* Token stored in easy-to-use format.
@ -55,29 +33,31 @@ export interface OAuthState {
app?: OAuthApp;
}
const initialState: OAuthState = {
loginState: 'none',
const initialState: LoginState = {
current: 'none',
expectingRedirect: false,
};
export const oauthSlice = createSlice({
name: "oauth",
export const loginSlice = createSlice({
name: "login",
initialState: initialState,
reducers: {
authorize: (_state, action: PayloadAction<OAuthState>) => {
authorize: (_state, action: PayloadAction<LoginState>) => {
// Overrides state with payload.
return action.payload;
},
setToken: (state, action: PayloadAction<OAuthToken>) => {
// Mark us as logged in by storing token.
setToken: (state, action: PayloadAction<OAuthAccessToken>) => {
// Mark us as logged
// in by storing token.
state.token = `${action.payload.token_type} ${action.payload.access_token}`;
state.loginState = "login";
state.current = "loggedin";
},
remove: (state) => {
// Mark us as logged out by clearing auth.
// Mark us as logged
// out by clearing auth.
delete state.token;
delete state.app;
state.loginState = "logout";
state.current = "loggedout";
}
}
});
@ -86,4 +66,4 @@ export const {
authorize,
setToken,
remove,
} = oauthSlice.actions;
} = loginSlice.actions;

View file

@ -30,19 +30,19 @@ import {
REGISTER,
} from "redux-persist";
import { oauthSlice } from "./oauth";
import { loginSlice } from "./login";
import { gtsApi } from "../lib/query/gts-api";
const combinedReducers = combineReducers({
[gtsApi.reducerPath]: gtsApi.reducer,
oauth: oauthSlice.reducer,
login: loginSlice.reducer,
});
const persistedReducer = persistReducer({
key: "gotosocial-settings",
storage: require("redux-persist/lib/storage").default,
stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel1").default,
whitelist: ["oauth"],
whitelist: ["login"],
migrate: async (state) => {
if (state == undefined) {
return state;
@ -51,8 +51,8 @@ const persistedReducer = persistReducer({
// This is a cheeky workaround for
// redux-persist being a stickler.
let anyState = state as any;
if (anyState?.oauth != undefined) {
anyState.oauth.expectingRedirect = false;
if (anyState?.login != undefined) {
anyState.login.expectingRedirect = false;
}
return anyState;