Figure out user manager for account user
This commit is contained in:
parent
3492558e06
commit
95a8e64fbb
16 changed files with 152 additions and 106 deletions
|
@ -36,14 +36,19 @@ import (
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO
|
TODO
|
||||||
use token auth in "SubscribeDialog"
|
|
||||||
upload files based on user limit
|
|
||||||
database migration
|
database migration
|
||||||
publishXHR + poll should pick current user, not from userManager
|
|
||||||
reserve topics
|
reserve topics
|
||||||
purge accounts that were not logged into in X
|
purge accounts that were not logged into in X
|
||||||
reset daily limits for users
|
reset daily limits for users
|
||||||
store users
|
"user list" shows * twice
|
||||||
|
"ntfy access everyone user4topic <bla>" twice -> UNIQUE constraint error
|
||||||
|
Account usage not updated "in real time"
|
||||||
|
Sync:
|
||||||
|
- "mute" setting
|
||||||
|
- figure out what settings are "web" or "phone"
|
||||||
|
UI:
|
||||||
|
- Subscription dotmenu dropdown: Move to nav bar, or make same as profile dropdown
|
||||||
|
- "Logout and delete local storage" option
|
||||||
Pages:
|
Pages:
|
||||||
- Home
|
- Home
|
||||||
- Password reset
|
- Password reset
|
||||||
|
@ -52,7 +57,6 @@ import (
|
||||||
-
|
-
|
||||||
Polishing:
|
Polishing:
|
||||||
aria-label for everything
|
aria-label for everything
|
||||||
|
|
||||||
Tests:
|
Tests:
|
||||||
- APIs
|
- APIs
|
||||||
- CRUD tokens
|
- CRUD tokens
|
||||||
|
|
|
@ -84,10 +84,10 @@ const (
|
||||||
`
|
`
|
||||||
selectTopicPermsQuery = `
|
selectTopicPermsQuery = `
|
||||||
SELECT read, write
|
SELECT read, write
|
||||||
FROM user_access
|
FROM user_access a
|
||||||
JOIN user ON user.user = '*' OR user.user = ?
|
JOIN user u ON u.id = a.user_id
|
||||||
WHERE ? LIKE user_access.topic
|
WHERE (u.user = '*' OR u.user = ?) AND ? LIKE a.topic
|
||||||
ORDER BY user.user DESC
|
ORDER BY u.user DESC
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,7 @@
|
||||||
"prefs_notifications_delete_after_one_month_description": "Notifications are auto-deleted after one month",
|
"prefs_notifications_delete_after_one_month_description": "Notifications are auto-deleted after one month",
|
||||||
"prefs_users_title": "Manage users",
|
"prefs_users_title": "Manage users",
|
||||||
"prefs_users_description": "Add/remove users for your protected topics here. Please note that username and password are stored in the browser's local storage.",
|
"prefs_users_description": "Add/remove users for your protected topics here. Please note that username and password are stored in the browser's local storage.",
|
||||||
|
"prefs_users_description_no_sync": "Users and passwords are not synchronized to your account.",
|
||||||
"prefs_users_table": "Users table",
|
"prefs_users_table": "Users table",
|
||||||
"prefs_users_add_button": "Add user",
|
"prefs_users_add_button": "Add user",
|
||||||
"prefs_users_edit_button": "Edit user",
|
"prefs_users_edit_button": "Edit user",
|
||||||
|
|
|
@ -6,8 +6,8 @@ import {
|
||||||
accountTokenUrl,
|
accountTokenUrl,
|
||||||
accountUrl,
|
accountUrl,
|
||||||
fetchLinesIterator,
|
fetchLinesIterator,
|
||||||
maybeWithBasicAuth,
|
withBasicAuth,
|
||||||
maybeWithBearerAuth,
|
withBearerAuth,
|
||||||
topicShortUrl,
|
topicShortUrl,
|
||||||
topicUrl,
|
topicUrl,
|
||||||
topicUrlAuth,
|
topicUrlAuth,
|
||||||
|
@ -31,7 +31,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Checking auth for ${url}`);
|
console.log(`[AccountApi] Checking auth for ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: maybeWithBasicAuth({}, user)
|
headers: withBasicAuth({}, user.username, user.password)
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -50,7 +50,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Logging out from ${url} using token ${token}`);
|
console.log(`[AccountApi] Logging out from ${url} using token ${token}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: maybeWithBearerAuth({}, token)
|
headers: withBearerAuth({}, token)
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -83,7 +83,7 @@ class AccountApi {
|
||||||
const url = accountUrl(config.baseUrl);
|
const url = accountUrl(config.baseUrl);
|
||||||
console.log(`[AccountApi] Fetching user account ${url}`);
|
console.log(`[AccountApi] Fetching user account ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: maybeWithBearerAuth({}, session.token())
|
headers: withBearerAuth({}, session.token())
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -100,7 +100,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Deleting user account ${url}`);
|
console.log(`[AccountApi] Deleting user account ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: maybeWithBearerAuth({}, session.token())
|
headers: withBearerAuth({}, session.token())
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -114,7 +114,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Changing account password ${url}`);
|
console.log(`[AccountApi] Changing account password ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: maybeWithBearerAuth({}, session.token()),
|
headers: withBearerAuth({}, session.token()),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
password: newPassword
|
password: newPassword
|
||||||
})
|
})
|
||||||
|
@ -131,7 +131,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Extending user access token ${url}`);
|
console.log(`[AccountApi] Extending user access token ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: maybeWithBearerAuth({}, session.token())
|
headers: withBearerAuth({}, session.token())
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -146,7 +146,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Updating user account ${url}: ${body}`);
|
console.log(`[AccountApi] Updating user account ${url}: ${body}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: maybeWithBearerAuth({}, session.token()),
|
headers: withBearerAuth({}, session.token()),
|
||||||
body: body
|
body: body
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
@ -162,7 +162,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Adding user subscription ${url}: ${body}`);
|
console.log(`[AccountApi] Adding user subscription ${url}: ${body}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: maybeWithBearerAuth({}, session.token()),
|
headers: withBearerAuth({}, session.token()),
|
||||||
body: body
|
body: body
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
@ -181,7 +181,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Updating user subscription ${url}: ${body}`);
|
console.log(`[AccountApi] Updating user subscription ${url}: ${body}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: maybeWithBearerAuth({}, session.token()),
|
headers: withBearerAuth({}, session.token()),
|
||||||
body: body
|
body: body
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
@ -199,7 +199,7 @@ class AccountApi {
|
||||||
console.log(`[AccountApi] Removing user subscription ${url}`);
|
console.log(`[AccountApi] Removing user subscription ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: maybeWithBearerAuth({}, session.token())
|
headers: withBearerAuth({}, session.token())
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
@ -208,6 +208,10 @@ class AccountApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
startWorker() {
|
startWorker() {
|
||||||
if (this.timer !== null) {
|
if (this.timer !== null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,9 +5,9 @@ import {
|
||||||
accountSubscriptionUrl,
|
accountSubscriptionUrl,
|
||||||
accountTokenUrl,
|
accountTokenUrl,
|
||||||
accountUrl,
|
accountUrl,
|
||||||
fetchLinesIterator,
|
fetchLinesIterator, maybeWithAuth,
|
||||||
maybeWithBasicAuth,
|
withBasicAuth,
|
||||||
maybeWithBearerAuth,
|
withBearerAuth,
|
||||||
topicShortUrl,
|
topicShortUrl,
|
||||||
topicUrl,
|
topicUrl,
|
||||||
topicUrlAuth,
|
topicUrlAuth,
|
||||||
|
@ -24,7 +24,7 @@ class Api {
|
||||||
? topicUrlJsonPollWithSince(baseUrl, topic, since)
|
? topicUrlJsonPollWithSince(baseUrl, topic, since)
|
||||||
: topicUrlJsonPoll(baseUrl, topic);
|
: topicUrlJsonPoll(baseUrl, topic);
|
||||||
const messages = [];
|
const messages = [];
|
||||||
const headers = maybeWithBasicAuth({}, user);
|
const headers = maybeWithAuth({}, user);
|
||||||
console.log(`[Api] Polling ${url}`);
|
console.log(`[Api] Polling ${url}`);
|
||||||
for await (let line of fetchLinesIterator(url, headers)) {
|
for await (let line of fetchLinesIterator(url, headers)) {
|
||||||
console.log(`[Api, ${shortUrl}] Received message ${line}`);
|
console.log(`[Api, ${shortUrl}] Received message ${line}`);
|
||||||
|
@ -45,7 +45,7 @@ class Api {
|
||||||
const response = await fetch(baseUrl, {
|
const response = await fetch(baseUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
headers: maybeWithBasicAuth(headers, user)
|
headers: maybeWithAuth(headers, user)
|
||||||
});
|
});
|
||||||
if (response.status < 200 || response.status > 299) {
|
if (response.status < 200 || response.status > 299) {
|
||||||
throw new Error(`Unexpected response: ${response.status}`);
|
throw new Error(`Unexpected response: ${response.status}`);
|
||||||
|
@ -111,7 +111,7 @@ class Api {
|
||||||
const url = topicUrlAuth(baseUrl, topic);
|
const url = topicUrlAuth(baseUrl, topic);
|
||||||
console.log(`[Api] Checking auth for ${url}`);
|
console.log(`[Api] Checking auth for ${url}`);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: maybeWithBasicAuth({}, user)
|
headers: maybeWithAuth({}, user)
|
||||||
});
|
});
|
||||||
if (response.status >= 200 && response.status <= 299) {
|
if (response.status >= 200 && response.status <= 299) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {basicAuth, encodeBase64Url, topicShortUrl, topicUrlWs} from "./utils";
|
import {basicAuth, bearerAuth, encodeBase64Url, topicShortUrl, topicUrlWs} from "./utils";
|
||||||
|
|
||||||
const retryBackoffSeconds = [5, 10, 15, 20, 30];
|
const retryBackoffSeconds = [5, 10, 15, 20, 30];
|
||||||
|
|
||||||
|
@ -96,12 +96,18 @@ class Connection {
|
||||||
params.push(`since=${this.since}`);
|
params.push(`since=${this.since}`);
|
||||||
}
|
}
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
const auth = encodeBase64Url(basicAuth(this.user.username, this.user.password));
|
params.push(`auth=${this.authParam()}`);
|
||||||
params.push(`auth=${auth}`);
|
|
||||||
}
|
}
|
||||||
const wsUrl = topicUrlWs(this.baseUrl, this.topic);
|
const wsUrl = topicUrlWs(this.baseUrl, this.topic);
|
||||||
return (params.length === 0) ? wsUrl : `${wsUrl}?${params.join('&')}`;
|
return (params.length === 0) ? wsUrl : `${wsUrl}?${params.join('&')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authParam() {
|
||||||
|
if (this.user.password) {
|
||||||
|
return encodeBase64Url(basicAuth(this.user.username, this.user.password));
|
||||||
|
}
|
||||||
|
return encodeBase64Url(bearerAuth(this.user.token));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConnectionState {
|
export class ConnectionState {
|
||||||
|
|
|
@ -109,7 +109,7 @@ class ConnectionManager {
|
||||||
|
|
||||||
const makeConnectionId = async (subscription, user) => {
|
const makeConnectionId = async (subscription, user) => {
|
||||||
return (user)
|
return (user)
|
||||||
? hashCode(`${subscription.id}|${user.username}|${user.password}`)
|
? hashCode(`${subscription.id}|${user.username}|${user.password ?? ""}|${user.token ?? ""}`)
|
||||||
: hashCode(`${subscription.id}`);
|
: hashCode(`${subscription.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
import db from "./db";
|
import db from "./db";
|
||||||
|
import session from "./Session";
|
||||||
|
|
||||||
class UserManager {
|
class UserManager {
|
||||||
async all() {
|
async all() {
|
||||||
return db.users.toArray();
|
const users = await db.users.toArray();
|
||||||
|
if (session.exists()) {
|
||||||
|
users.unshift(this.localUser());
|
||||||
|
}
|
||||||
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(baseUrl) {
|
async get(baseUrl) {
|
||||||
|
if (session.exists() && baseUrl === config.baseUrl) {
|
||||||
|
return this.localUser();
|
||||||
|
}
|
||||||
return db.users.get(baseUrl);
|
return db.users.get(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(user) {
|
async save(user) {
|
||||||
|
if (user.baseUrl === config.baseUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await db.users.put(user);
|
await db.users.put(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(baseUrl) {
|
async delete(baseUrl) {
|
||||||
|
if (session.exists() && baseUrl === config.baseUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await db.users.delete(baseUrl);
|
await db.users.delete(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localUser() {
|
||||||
|
if (!session.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
baseUrl: config.baseUrl,
|
||||||
|
username: session.username(),
|
||||||
|
token: session.token() // Not "password"!
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userManager = new UserManager();
|
const userManager = new UserManager();
|
||||||
|
|
|
@ -99,17 +99,17 @@ export const unmatchedTags = (tags) => {
|
||||||
else return tags.filter(tag => !(tag in emojis));
|
else return tags.filter(tag => !(tag in emojis));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maybeWithBasicAuth = (headers, user) => {
|
export const maybeWithAuth = (headers, user) => {
|
||||||
if (user) {
|
if (user && user.password) {
|
||||||
headers['Authorization'] = `Basic ${encodeBase64(`${user.username}:${user.password}`)}`;
|
return withBasicAuth(headers, user.username, user.password);
|
||||||
|
} else if (user && user.token) {
|
||||||
|
return withBearerAuth(headers, user.token);
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maybeWithBearerAuth = (headers, token) => {
|
export const withBasicAuth = (headers, username, password) => {
|
||||||
if (token) {
|
headers['Authorization'] = basicAuth(username, password);
|
||||||
headers['Authorization'] = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,15 @@ export const basicAuth = (username, password) => {
|
||||||
return `Basic ${encodeBase64(`${username}:${password}`)}`;
|
return `Basic ${encodeBase64(`${username}:${password}`)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const withBearerAuth = (headers, token) => {
|
||||||
|
headers['Authorization'] = bearerAuth(token);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bearerAuth = (token) => {
|
||||||
|
return `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const encodeBase64 = (s) => {
|
export const encodeBase64 = (s) => {
|
||||||
return Base64.encode(s);
|
return Base64.encode(s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ const Stats = () => {
|
||||||
</div>
|
</div>
|
||||||
<LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} />
|
<LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} />
|
||||||
</Pref>
|
</Pref>
|
||||||
<Pref labelId={"attachments"} title={t("Attachment storage")} subtitle={t("5 MB per file")}>
|
<Pref labelId={"attachments"} title={t("Attachment storage")} subtitle={t("{{filesize}} per file", { filesize: formatBytes(account.limits.attachment_file_size) })}>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
|
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
|
||||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("of {{limit}}", { limit: formatBytes(account.limits.attachment_total_size) }) : t("Unlimited")}</Typography>
|
<Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("of {{limit}}", { limit: formatBytes(account.limits.attachment_total_size) }) : t("Unlimited")}</Typography>
|
||||||
|
@ -153,8 +153,7 @@ const ChangePassword = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Account] Error changing password`, e);
|
console.log(`[Account] Error changing password`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
// TODO show error
|
// TODO show error
|
||||||
}
|
}
|
||||||
|
@ -238,13 +237,11 @@ const DeleteAccount = () => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
console.debug(`[Account] Account deleted`);
|
console.debug(`[Account] Account deleted`);
|
||||||
// TODO delete local storage
|
// TODO delete local storage
|
||||||
session.reset();
|
session.resetAndRedirect(routes.app);
|
||||||
window.location.href = routes.app;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Account] Error deleting account`, e);
|
console.log(`[Account] Error deleting account`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
// TODO show error
|
// TODO show error
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,8 +124,7 @@ const SettingsIcons = (props) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[ActionBar] Error unsubscribing`, e);
|
console.log(`[ActionBar] Error unsubscribing`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,8 +271,7 @@ const ProfileIcon = (props) => {
|
||||||
try {
|
try {
|
||||||
await accountApi.logout();
|
await accountApi.logout();
|
||||||
} finally {
|
} finally {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.app);
|
||||||
window.location.href = routes.app;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -123,8 +123,7 @@ const Layout = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[App] Error fetching account`, e);
|
console.log(`[App] Error fetching account`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -270,6 +270,7 @@ const Users = () => {
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
{t("prefs_users_description")}
|
{t("prefs_users_description")}
|
||||||
|
{session.exists() && <>{" " + t("prefs_users_description_no_sync")}</>}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
{users?.length > 0 && <UserTable users={users}/>}
|
{users?.length > 0 && <UserTable users={users}/>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -319,52 +320,49 @@ const UserTable = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<Table size="small" aria-label={t("prefs_users_table")}>
|
||||||
<Table size="small" aria-label={t("prefs_users_table")}>
|
<TableHead>
|
||||||
<TableHead>
|
<TableRow>
|
||||||
<TableRow>
|
<TableCell sx={{paddingLeft: 0}}>{t("prefs_users_table_user_header")}</TableCell>
|
||||||
<TableCell sx={{paddingLeft: 0}}>{t("prefs_users_table_user_header")}</TableCell>
|
<TableCell>{t("prefs_users_table_base_url_header")}</TableCell>
|
||||||
<TableCell>{t("prefs_users_table_base_url_header")}</TableCell>
|
<TableCell/>
|
||||||
<TableCell/>
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{props.users?.map(user => (
|
||||||
|
<TableRow
|
||||||
|
key={user.baseUrl}
|
||||||
|
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||||
|
>
|
||||||
|
<TableCell component="th" scope="row" sx={{paddingLeft: 0}}
|
||||||
|
aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
|
||||||
|
<TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
{user.baseUrl !== config.baseUrl &&
|
||||||
|
<>
|
||||||
|
<IconButton onClick={() => handleEditClick(user)}
|
||||||
|
aria-label={t("prefs_users_edit_button")}>
|
||||||
|
<EditIcon/>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleDeleteClick(user)}
|
||||||
|
aria-label={t("prefs_users_delete_button")}>
|
||||||
|
<CloseIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
))}
|
||||||
<TableBody>
|
</TableBody>
|
||||||
{props.users?.map(user => (
|
<UserDialog
|
||||||
<TableRow
|
key={`userEditDialog${dialogKey}`}
|
||||||
key={user.baseUrl}
|
open={dialogOpen}
|
||||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
user={dialogUser}
|
||||||
>
|
users={props.users}
|
||||||
<TableCell component="th" scope="row" sx={{paddingLeft: 0}}
|
onCancel={handleDialogCancel}
|
||||||
aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
|
onSubmit={handleDialogSubmit}
|
||||||
<TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
|
/>
|
||||||
<TableCell align="right">
|
</Table>
|
||||||
<IconButton onClick={() => handleEditClick(user)}
|
|
||||||
aria-label={t("prefs_users_edit_button")}>
|
|
||||||
<EditIcon/>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={() => handleDeleteClick(user)}
|
|
||||||
aria-label={t("prefs_users_delete_button")}>
|
|
||||||
<CloseIcon/>
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
<UserDialog
|
|
||||||
key={`userEditDialog${dialogKey}`}
|
|
||||||
open={dialogOpen}
|
|
||||||
user={dialogUser}
|
|
||||||
users={props.users}
|
|
||||||
onCancel={handleDialogCancel}
|
|
||||||
onSubmit={handleDialogSubmit}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
{session.exists() &&
|
|
||||||
<Typography>
|
|
||||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,15 @@ import IconButton from "@mui/material/IconButton";
|
||||||
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon';
|
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon';
|
||||||
import {Close} from "@mui/icons-material";
|
import {Close} from "@mui/icons-material";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import {formatBytes, maybeWithBasicAuth, topicShortUrl, topicUrl, validTopic, validUrl} from "../app/utils";
|
import {
|
||||||
|
formatBytes,
|
||||||
|
maybeWithAuth,
|
||||||
|
withBasicAuth,
|
||||||
|
topicShortUrl,
|
||||||
|
topicUrl,
|
||||||
|
validTopic,
|
||||||
|
validUrl
|
||||||
|
} from "../app/utils";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import AttachmentIcon from "./AttachmentIcon";
|
import AttachmentIcon from "./AttachmentIcon";
|
||||||
import DialogFooter from "./DialogFooter";
|
import DialogFooter from "./DialogFooter";
|
||||||
|
@ -132,7 +140,7 @@ const PublishDialog = (props) => {
|
||||||
const body = (attachFile) ? attachFile : message;
|
const body = (attachFile) ? attachFile : message;
|
||||||
try {
|
try {
|
||||||
const user = await userManager.get(baseUrl);
|
const user = await userManager.get(baseUrl);
|
||||||
const headers = maybeWithBasicAuth({}, user);
|
const headers = maybeWithAuth({}, user);
|
||||||
const progressFn = (ev) => {
|
const progressFn = (ev) => {
|
||||||
if (ev.loaded > 0 && ev.total > 0) {
|
if (ev.loaded > 0 && ev.total > 0) {
|
||||||
setStatus(t("publish_dialog_progress_uploading_detail", {
|
setStatus(t("publish_dialog_progress_uploading_detail", {
|
||||||
|
@ -180,8 +188,7 @@ const PublishDialog = (props) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[PublishDialog] Retrieving attachment limits failed`, e);
|
console.log(`[PublishDialog] Retrieving attachment limits failed`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
} else {
|
} else {
|
||||||
setAttachFileError(""); // Reset error (rely on server-side checking)
|
setAttachFileError(""); // Reset error (rely on server-side checking)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,7 @@ const SubscribeDialog = (props) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e);
|
console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,7 @@ export const useAutoSubscribe = (subscriptions, selected) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[App] Auto-subscribing failed`, e);
|
console.log(`[App] Auto-subscribing failed`, e);
|
||||||
if ((e instanceof UnauthorizedError)) {
|
if ((e instanceof UnauthorizedError)) {
|
||||||
session.reset();
|
session.resetAndRedirect(routes.login);
|
||||||
window.location.href = routes.login;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue