diff --git a/client/src/components/AdminSettings/AccountsOverview.tsx b/client/src/components/AdminSettings/AccountsOverview.tsx index 970a2f689fcd49f395bcd85eba0539b39be29336..770db7ceeb357c8bb7a83886bcd4332eb2a6b424 100644 --- a/client/src/components/AdminSettings/AccountsOverview.tsx +++ b/client/src/components/AdminSettings/AccountsOverview.tsx @@ -76,6 +76,12 @@ const headCells: readonly HeadCell[] = [ disablePadding: true, label: t('admin_page_accounts_overview_title_username'), }, + { + id: 'accountId', + numeric: true, + disablePadding: false, + label: t('account_id'), + }, { id: 'auth', numeric: true, @@ -298,6 +304,7 @@ export default function AccountsOverview() { {row.username} <Box sx={{ fontSize: '11px', color: 'grey' }}>{row.accountUri}</Box> </TableCell> + <TableCell align="right">{row.accountId}</TableCell> <TableCell align="right">{row.auth}</TableCell> <TableCell align="right">{String(byteSize(row.storage))}</TableCell> <TableCell padding="checkbox"> diff --git a/common/src/index.ts b/common/src/index.ts index d1d7afe3e21e324a5a31253d5cf551b1e79228d6..5e9265b87b7c01590bd74391d4cba3bd2e1710d9 100644 --- a/common/src/index.ts +++ b/common/src/index.ts @@ -32,7 +32,6 @@ export * from './interfaces/conversation.js' export * from './interfaces/link-preview.js' export * from './interfaces/linked-devices.js' export * from './interfaces/lookup-result.js' -export * from './interfaces/open-id-account.js' export * from './interfaces/websocket-interfaces.js' export * from './interfaces/websocket-message.js' export * from './utils/utils.js' diff --git a/common/src/interfaces/admin.ts b/common/src/interfaces/admin.ts index 31bc2a57e78468b48af54da1c7af09a407854a55..978b7063cb6e22ac83f01e4ba39f5826f4131dd9 100644 --- a/common/src/interfaces/admin.ts +++ b/common/src/interfaces/admin.ts @@ -67,4 +67,6 @@ export interface AccountOverview { accountUri: string auth: string storage: number + accountId: string + expirationDate?: number } diff --git a/common/src/interfaces/open-id-account.ts b/common/src/interfaces/open-id-account.ts deleted file mode 100644 index f8295b7d3557f97e093117c7a5f283c4bb08ea1c..0000000000000000000000000000000000000000 --- a/common/src/interfaces/open-id-account.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022-2025 Savoir-faire Linux Inc. - * - * 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 - * <https://www.gnu.org/licenses/>. - */ - -export interface OpenIdAccount { - accountId: string - service: string[] -} diff --git a/server/locale/en/translation.json b/server/locale/en/translation.json index 5ab4df86ea7ad67a759200937d5b48560ce00bfa..45ea7291d4d42f83aa375c331c8c24e484bbbd46 100644 --- a/server/locale/en/translation.json +++ b/server/locale/en/translation.json @@ -6,6 +6,7 @@ "accept_call_audio": "Accept with audio", "accept_call_video": "Accept with video", "admin": "Administrator", + "account_id": "Account Identifier", "accounts": "Accounts", "add_comment": "Add a comment", "add_to_group_validation": "Add to group", @@ -16,7 +17,7 @@ "admin_login_to_main": "User login", "admin_page_accounts_overview_title": "Accounts overview - {{count}} active accounts", "admin_page_accounts_overview_title_auth": "Authentication method", - "admin_page_accounts_overview_title_username": "Username/Identifier", + "admin_page_accounts_overview_title_username": "Username", "admin_page_accounts_overview_title_storage": "Storage used", "admin_page_create_button": "Create admin account", "admin_page_login_title": "Jami Admin", diff --git a/server/src/routers/admin-router.ts b/server/src/routers/admin-router.ts index 8cc9f457e92bcb215cd005fe59729580bd40f6f2..67a09ba1208df6003181883954e6b17674238323 100644 --- a/server/src/routers/admin-router.ts +++ b/server/src/routers/admin-router.ts @@ -329,6 +329,7 @@ adminRouter.get( accountUri, auth: 'jams', storage: await getSizeOnDisk(path), + accountId: account.accountId, }) } } @@ -343,6 +344,7 @@ adminRouter.get( accountUri, auth: 'local', storage: await getSizeOnDisk(path), + accountId, }) } } @@ -366,10 +368,28 @@ adminRouter.get( accountUri, auth: account.service.join(', '), storage: await getSizeOnDisk(path), + accountId: account.accountId, }) alreadyAdded.set(account.accountId, username) } } + + for (const [accountId, expirationDate] of Object.entries(accounts.getGuests())) { + const accountUri = jamid.getAccountDetails(accountId)['Account.username'] + if (accountUri) { + const path = dataPath + '/' + accountId + accountsList.push({ + username: accountId, + accountUri, + auth: 'guest', + storage: await getSizeOnDisk(path), + accountId, + expirationDate: Number(expirationDate), + }) + alreadyAdded.set(accountId, accountUri) + } + } + res.send(accountsList) }), ) diff --git a/server/src/routers/auth-router.ts b/server/src/routers/auth-router.ts index 0a0f748d06cbebc752a51bbb309fa3d3737070d0..537873c33f602466016283ac7fa9e0522727367c 100644 --- a/server/src/routers/auth-router.ts +++ b/server/src/routers/auth-router.ts @@ -97,7 +97,7 @@ authRouter.post( } try { - accounts.set(username, hashedPassword, 'local') + accounts.set(username, hashedPassword, 'local', accountId) await accounts.save() res.sendStatus(HttpStatusCode.Created) } catch (e: any) { @@ -118,13 +118,17 @@ authRouter.post( } const { accountId } = await jamid.addAccount({}) - const details = jamid.getAccountDetails(accountId) - const username = details['Account.username'] - const jwt = await signJwtWithExpiration(accountId, '2h') + const sessionTime = 2 * 60 * 60 * 1000 const date = Date.now() + sessionTime - accounts.set(accountId, date.toString(), 'guest') + + accounts.set(accountId, date.toString(), 'guest', '') accounts.save() + + const details = jamid.getAccountDetails(accountId) + const username = details['Account.username'] + const jwt = await signJwtWithExpiration(accountId, '2h') + res.send({ accessToken: jwt }) setTimeout(() => { clearGuest(accountId, username) @@ -209,12 +213,25 @@ authRouter.post( return } - const accountId = jamid.getAccountIdFromUsername(username) + const data = accounts.get(username) + if (typeof data === 'string' || data === undefined) { + res.status(HttpStatusCode.NotFound).send('Username not found') + return + } + + const accountId = data?.accountId if (accountId === undefined) { res.status(HttpStatusCode.NotFound).send('Username not found') return } - const hashedPassword = accounts.get(username, 'local') + + if (!('password' in data)) { + res.status(HttpStatusCode.NotFound).send('Password not found') + return + } + + const hashedPassword = data.password + if (hashedPassword === undefined || typeof hashedPassword !== 'string') { res .status(HttpStatusCode.Unauthorized) @@ -240,7 +257,7 @@ authRouter.post( const accountId = res.locals.accountId const jwt = await signJwtWithExpiration(accountId, '2h') const date = Date.now() + 2 * 60 * 60 * 1000 - accounts.set(accountId, date.toString(), 'guest') + accounts.set(accountId, date.toString(), 'guest', '') accounts.save() res.send({ accessToken: jwt }) }), @@ -372,7 +389,7 @@ authRouter.get( const savedAccount = accounts.getOpenIdAccount(key) if (savedAccount !== undefined && accounts.getOpenIdAccount(key) === undefined) { - accounts.set(key, { accountId: savedAccount.accountId, service: [provider] }, 'openid') // now the user can cannot using either the sub or the email + accounts.set(key, [provider], 'openid', savedAccount.accountId) // now the user can cannot using either the sub or the email accounts.save() } diff --git a/server/src/storage/accounts.ts b/server/src/storage/accounts.ts index 89247612e0271c96f57bb2df686d1e58ea9b50af..fbda83aa2927a60124334d8d544e1d52d01005d1 100644 --- a/server/src/storage/accounts.ts +++ b/server/src/storage/accounts.ts @@ -19,7 +19,6 @@ import { existsSync, mkdirSync, readFileSync } from 'node:fs' import { writeFile } from 'node:fs/promises' import envPaths from 'env-paths' -import { OpenIdAccount } from 'jami-web-common' import { Service } from 'typedi' const paths = envPaths('jami-web', { suffix: '' }) @@ -29,10 +28,16 @@ if (!existsSync(paths.data)) { } interface AccountsFormat { - local: Record<string, string> + local: Record<string, { password: string; accountId: string }> jams: Record<string, { password: string; accountId: string }> - guest: Record<string, string> - openid: Record<string, OpenIdAccount> + guest: Record<string, string> // key is accountId, value is expiration date + openid: Record< + string, + { + accountId: string + service: string[] + } + > } const METHODS = ['local', 'jams', 'guest', 'openid'] @@ -51,6 +56,7 @@ export class Accounts { } this.accounts = JSON.parse(buffer.toString()) } + get(username: string, authMethod: string = 'local') { if (!METHODS.includes(authMethod)) { console.log('Invalid auth method') @@ -62,15 +68,15 @@ export class Accounts { return this.accounts[authMethod as keyof AccountsFormat][username.toLowerCase()] } - getOpenIdAccount(email: string) { - return this.accounts['openid'][email.toLowerCase()] + getOpenIdAccount(key: string) { + return this.accounts['openid'][key.toLowerCase()] } isInJamsAccounts(username: string) { return username.toLowerCase() in this.accounts['jams'] } - set(username: string, value: string | OpenIdAccount, authMethod: string = 'local', JAMSAccountId?: string): void { + set(username: string, value: string | string[], authMethod: string = 'local', accountId: string): void { if (!METHODS.includes(authMethod)) { console.log('Invalid auth method') return @@ -83,10 +89,10 @@ export class Accounts { try { switch (authMethod) { case 'jams': - if (JAMSAccountId !== undefined && typeof value === 'string') { + if (accountId !== undefined && typeof value === 'string') { this.accounts['jams'][username.toLowerCase()] = { password: value, - accountId: JAMSAccountId, + accountId: accountId, } } else { throw new Error('JAMSAccountId is required for jams auth method') @@ -96,9 +102,11 @@ export class Accounts { if (typeof value !== 'string') { return } - this.accounts['local'][username.toLowerCase()] = value + this.accounts['local'][username.toLowerCase()] = { + password: value, + accountId: accountId, + } break - // TODO : Add support for guest and openid case 'guest': if (typeof value !== 'string') { return @@ -109,7 +117,11 @@ export class Accounts { if (typeof value === 'string') { return } - this.accounts['openid'][username.toLowerCase()] = value + this.accounts['openid'][username.toLowerCase()] = { + service: value, + accountId: accountId, + } + break default: throw new Error('Invalid auth method') @@ -137,6 +149,9 @@ export class Accounts { } extendSessionIfGuest(accountId: string) { + if (!(accountId in this.accounts.guest)) { + return + } const guestExpiration = this.accounts.guest[accountId] if (guestExpiration === undefined) { return diff --git a/server/src/utils/openid.ts b/server/src/utils/openid.ts index 69440f1dc1cbcbc1e79d188372386008f9c88dbd..014b999fe2098d1252c34c1c4892eabf16ee5055 100644 --- a/server/src/utils/openid.ts +++ b/server/src/utils/openid.ts @@ -16,7 +16,7 @@ * <https://www.gnu.org/licenses/>. */ import axios from 'axios' -import { AccountDetails, OpenIdAccount } from 'jami-web-common' +import { AccountDetails } from 'jami-web-common' import { Container } from 'typedi' import { Jamid } from '../jamid/jamid.js' @@ -105,12 +105,7 @@ export async function getJwt(email: string, userName: string, service: string) { return 'INVALID' } - const values: OpenIdAccount = { - accountId: accId, - service: [service], - } - - accounts.set(email, values, 'openid') + accounts.set(email, [service], 'openid', accId) await accounts.save() const jwt = await signJwt(accId) @@ -121,7 +116,7 @@ export async function getJwt(email: string, userName: string, service: string) { if (accountId && accountId !== '') { if (!accountData.service.includes(service)) { accountData.service.push(service) - accounts.set(email, accountData, 'openid') + accounts.set(email, accountData.service, 'openid', accountId) accounts.save() } const jwt = await signJwt(accountId)