mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-01-01 02:29:32 -05:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5baede08a7 | ||
|
|
34995fe801 | ||
|
|
92a2511d9d | ||
|
|
41486c940c | ||
|
|
47c77ade02 | ||
|
|
a51621970d | ||
|
|
39f339b659 | ||
|
|
65d1ca1564 | ||
|
|
5c010cd873 | ||
|
|
88ba57ce88 | ||
|
|
4d266beb0d | ||
|
|
536688d110 | ||
|
|
e343b48fe7 |
@@ -7,6 +7,7 @@ on:
|
||||
branches: [ "main" ]
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
chrome-extension:
|
||||
@@ -60,11 +61,10 @@ jobs:
|
||||
- name: Zip Chrome Extension
|
||||
run: |
|
||||
cd browser-extensions/chrome/dist
|
||||
zip -r ../../aliasvault-chrome-extension.zip .
|
||||
zip -r ../../../aliasvault-chrome-extension.zip .
|
||||
|
||||
- name: Upload Chrome Extension ZIP to Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: aliasvault-chrome-extension.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -58,7 +58,7 @@ This method uses pre-built Docker images and works on minimal hardware specifica
|
||||
|
||||
```bash
|
||||
# Download install script from latest stable release
|
||||
curl -o install.sh https://raw.githubusercontent.com/lanedirt/AliasVault/0.12.0/install.sh
|
||||
curl -o install.sh https://raw.githubusercontent.com/lanedirt/AliasVault/0.12.2/install.sh
|
||||
|
||||
# Make install script executable and run it. This will create the .env file, pull the Docker images, and start the AliasVault containers.
|
||||
chmod +x install.sh
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "AliasVault",
|
||||
"description": "AliasVault Browser AutoFill Extension. Keeping your personal information private.",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.2",
|
||||
"manifest_version": 3,
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
|
||||
|
||||
@@ -58,7 +58,8 @@ const App: React.FC = () => {
|
||||
useEffect(() => {
|
||||
if (authContext.globalMessage) {
|
||||
setMessage(authContext.globalMessage);
|
||||
authContext.clearGlobalMessage();
|
||||
} else {
|
||||
setMessage(null);
|
||||
}
|
||||
}, [authContext, authContext.globalMessage]);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const Header: React.FC<HeaderProps> = ({
|
||||
const openClientTab = async () : Promise<void> => {
|
||||
const setting = await chrome.storage.local.get(['clientUrl']);
|
||||
let clientUrl = AppInfo.DEFAULT_CLIENT_URL;
|
||||
if (setting.clientUrl.length > 0) {
|
||||
if (setting.clientUrl && setting.clientUrl.length > 0) {
|
||||
clientUrl = setting.clientUrl;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ type DbContextType = {
|
||||
initializeDatabase: (vaultResponse: VaultResponse, derivedKey: string) => Promise<void>;
|
||||
clearDatabase: () => void;
|
||||
vaultRevision: number;
|
||||
publicEmailDomains: string[];
|
||||
privateEmailDomains: string[];
|
||||
}
|
||||
|
||||
const DbContext = createContext<DbContextType | undefined>(undefined);
|
||||
@@ -35,7 +37,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
/**
|
||||
* Public email domains.
|
||||
*/
|
||||
const [, setPublicEmailDomains] = useState<string[]>([]);
|
||||
const [publicEmailDomains, setPublicEmailDomains] = useState<string[]>([]);
|
||||
|
||||
/**
|
||||
* Vault revision.
|
||||
@@ -45,7 +47,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
/**
|
||||
* Private email domains.
|
||||
*/
|
||||
const [, setPrivateEmailDomains] = useState<string[]>([]);
|
||||
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
||||
|
||||
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse, derivedKey: string) => {
|
||||
// Attempt to decrypt the blob.
|
||||
@@ -123,8 +125,10 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
dbAvailable,
|
||||
initializeDatabase,
|
||||
clearDatabase,
|
||||
vaultRevision
|
||||
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, clearDatabase, vaultRevision]);
|
||||
vaultRevision,
|
||||
publicEmailDomains,
|
||||
privateEmailDomains
|
||||
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, clearDatabase, vaultRevision, publicEmailDomains, privateEmailDomains]);
|
||||
|
||||
return (
|
||||
<DbContext.Provider value={contextValue}>
|
||||
|
||||
@@ -44,6 +44,30 @@ const CredentialDetails: React.FC = () => {
|
||||
window.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the email domain is supported for email preview.
|
||||
*
|
||||
* @param email The email address to check
|
||||
* @returns True if the domain is supported, false otherwise
|
||||
*/
|
||||
const isEmailDomainSupported = (email: string): boolean => {
|
||||
// Extract domain from email
|
||||
const domain = email.split('@')[1]?.toLowerCase();
|
||||
|
||||
if (!domain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if domain is in public or private domains
|
||||
const publicDomains = dbContext.publicEmailDomains ?? [];
|
||||
const privateDomains = dbContext.privateEmailDomains ?? [];
|
||||
|
||||
// Check if the domain ends with any of the supported domains
|
||||
return [...publicDomains, ...privateDomains].some(supportedDomain =>
|
||||
domain === supportedDomain || domain.endsWith(`.${supportedDomain}`)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// For popup windows, ensure we have proper history state for navigation
|
||||
if (isPopup()) {
|
||||
@@ -121,11 +145,15 @@ const CredentialDetails: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{credential.Email && (
|
||||
<div className="mt-6">
|
||||
<EmailPreview
|
||||
email={credential.Email}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
{isEmailDomainSupported(credential.Email) && (
|
||||
<div className="mt-6">
|
||||
<EmailPreview
|
||||
email={credential.Email}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useMinDurationLoading } from '../hooks/useMinDurationLoading';
|
||||
import EncryptionUtility from '../../shared/EncryptionUtility';
|
||||
import { Attachment } from '../../shared/types/webapi/Attachment';
|
||||
import { useLoading } from '../context/LoadingContext';
|
||||
import ConversionUtility from '../utils/ConversionUtility';
|
||||
|
||||
/**
|
||||
* Email details page.
|
||||
@@ -227,7 +228,7 @@ const EmailDetails: React.FC = () => {
|
||||
<div className="bg-white">
|
||||
{email.messageHtml ? (
|
||||
<iframe
|
||||
srcDoc={email.messageHtml}
|
||||
srcDoc={ConversionUtility.convertAnchorTagsToOpenInNewTab(email.messageHtml)}
|
||||
className="w-full min-h-[500px] border-0"
|
||||
title="Email content"
|
||||
/>
|
||||
|
||||
@@ -34,13 +34,8 @@ const EmailsList: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: create separate query to only get email addresses to avoid loading all credentials.
|
||||
const credentials = dbContext.sqliteClient.getAllCredentials();
|
||||
|
||||
// Get unique email addresses from all credentials.
|
||||
const emailAddresses = credentials
|
||||
.map(cred => cred.Email.trim()) // Trim whitespace
|
||||
.filter((email, index, self) => self.indexOf(email) === index);
|
||||
const emailAddresses = dbContext.sqliteClient.getAllEmailAddresses();
|
||||
|
||||
try {
|
||||
// For now we only show the latest 50 emails. No pagination.
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useLoading } from '../context/LoadingContext';
|
||||
import { VaultResponse } from '../../shared/types/webapi/VaultResponse';
|
||||
import { LoginResponse } from '../../shared/types/webapi/Login';
|
||||
import LoginServerInfo from '../components/LoginServerInfo';
|
||||
import { AppInfo } from '../../shared/AppInfo';
|
||||
|
||||
/**
|
||||
* Login page
|
||||
@@ -39,7 +40,12 @@ const Login: React.FC = () => {
|
||||
*/
|
||||
const loadClientUrl = async () : Promise<void> => {
|
||||
const setting = await chrome.storage.local.get(['clientUrl']);
|
||||
setClientUrl(setting.clientUrl);
|
||||
let clientUrl = AppInfo.DEFAULT_CLIENT_URL;
|
||||
if (setting.clientUrl && setting.clientUrl.length > 0) {
|
||||
clientUrl = setting.clientUrl;
|
||||
}
|
||||
|
||||
setClientUrl(clientUrl);
|
||||
};
|
||||
loadClientUrl();
|
||||
}, []);
|
||||
@@ -54,6 +60,9 @@ const Login: React.FC = () => {
|
||||
try {
|
||||
showLoading();
|
||||
|
||||
// Clear global message if set with every login attempt.
|
||||
authContext.clearGlobalMessage();
|
||||
|
||||
// Use the srpUtil instance instead of the imported singleton
|
||||
const loginResponse = await srpUtil.initiateLogin(credentials.username);
|
||||
|
||||
@@ -121,9 +130,8 @@ const Login: React.FC = () => {
|
||||
|
||||
// Show app.
|
||||
hideLoading();
|
||||
} catch (err) {
|
||||
setError('Login failed. Please check your credentials and try again.');
|
||||
console.error('Login error:', err);
|
||||
} catch {
|
||||
setError('Could not reach AliasVault server. Please try again later or contact support if the problem persists.');
|
||||
hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Utility class for conversion operations.
|
||||
*/
|
||||
class ConversionUtility {
|
||||
/**
|
||||
* Convert all anchor tags to open in a new tab.
|
||||
* @param html HTML input.
|
||||
* @returns HTML with all anchor tags converted to open in a new tab when clicked on.
|
||||
*
|
||||
* Note: same implementation exists in c-sharp version in AliasVault.Shared.Utilities.ConversionUtility.cs
|
||||
*/
|
||||
public convertAnchorTagsToOpenInNewTab(html: string): string {
|
||||
try {
|
||||
// Create a DOM parser
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
// Select all anchor tags with href attribute
|
||||
const anchors = doc.querySelectorAll('a[href]');
|
||||
|
||||
if (anchors.length > 0) {
|
||||
anchors.forEach((anchor: Element) => {
|
||||
// Handle target attribute
|
||||
if (!anchor.hasAttribute('target')) {
|
||||
anchor.setAttribute('target', '_blank');
|
||||
} else if (anchor.getAttribute('target') !== '_blank') {
|
||||
anchor.setAttribute('target', '_blank');
|
||||
}
|
||||
|
||||
// Handle rel attribute for security
|
||||
if (!anchor.hasAttribute('rel')) {
|
||||
anchor.setAttribute('rel', 'noopener noreferrer');
|
||||
} else {
|
||||
const relValue = anchor.getAttribute('rel') ?? '';
|
||||
const relValues = new Set(relValue.split(' ').filter(val => val.trim() !== ''));
|
||||
|
||||
relValues.add('noopener');
|
||||
relValues.add('noreferrer');
|
||||
|
||||
anchor.setAttribute('rel', Array.from(relValues).join(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return doc.documentElement.outerHTML;
|
||||
} catch (ex) {
|
||||
// Log the exception
|
||||
console.error(`Error in convertAnchorTagsToOpenInNewTab: ${ex instanceof Error ? ex.message : String(ex)}`);
|
||||
|
||||
// Return the original HTML if an error occurs
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConversionUtility();
|
||||
@@ -6,7 +6,7 @@ export class AppInfo {
|
||||
/**
|
||||
* The current extension version. This should be updated with each release of the extension.
|
||||
*/
|
||||
public static readonly VERSION = '0.12.0';
|
||||
public static readonly VERSION = '0.12.2';
|
||||
|
||||
/**
|
||||
* The minimum supported AliasVault server (API) version. If the server version is below this, the
|
||||
|
||||
@@ -229,6 +229,25 @@ class SqliteClient {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all unique email addresses from all credentials.
|
||||
* @returns Array of email addresses.
|
||||
*/
|
||||
public getAllEmailAddresses(): string[] {
|
||||
const query = `
|
||||
SELECT DISTINCT
|
||||
a.Email
|
||||
FROM Credentials c
|
||||
LEFT JOIN Aliases a ON c.AliasId = a.Id
|
||||
WHERE a.Email IS NOT NULL AND a.Email != '' AND c.IsDeleted = 0
|
||||
`;
|
||||
|
||||
const results = this.executeQuery(query);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return results.map((row: any) => row.Email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all encryption keys.
|
||||
*/
|
||||
|
||||
@@ -21,18 +21,18 @@ export class WebApiService {
|
||||
*
|
||||
* @param {Function} handleLogout - Function to handle logout.
|
||||
*/
|
||||
public constructor(
|
||||
private readonly handleLogout: () => void
|
||||
) {
|
||||
// Remove initialization of baseUrl
|
||||
}
|
||||
public constructor(private readonly handleLogout: () => void) { }
|
||||
|
||||
/**
|
||||
* Get the base URL for the API from settings.
|
||||
*/
|
||||
private async getBaseUrl(): Promise<string> {
|
||||
const result = await chrome.storage.local.get(['apiUrl']);
|
||||
return (result.apiUrl ?? AppInfo.DEFAULT_API_URL).replace(/\/$/, '') + '/v1/';
|
||||
if (result.apiUrl && result.apiUrl.length > 0) {
|
||||
return result.apiUrl.replace(/\/$/, '') + '/v1/';
|
||||
}
|
||||
|
||||
return AppInfo.DEFAULT_API_URL.replace(/\/$/, '') + '/v1/';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,13 +215,29 @@ export class WebApiService {
|
||||
* Calls the status endpoint to check if the auth tokens are still valid, app is supported and the vault is up to date.
|
||||
*/
|
||||
public async getStatus(): Promise<StatusResponse> {
|
||||
return await this.get<StatusResponse>('Auth/status');
|
||||
try {
|
||||
return await this.get<StatusResponse>('Auth/status');
|
||||
} catch {
|
||||
/**
|
||||
* If the status endpoint is not available, return a default status response which will trigger
|
||||
* a logout and error message.
|
||||
*/
|
||||
return {
|
||||
clientVersionSupported: true,
|
||||
serverVersion: '0.0.0',
|
||||
vaultRevision: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the status response and returns an error message if validation fails.
|
||||
*/
|
||||
public validateStatusResponse(statusResponse: StatusResponse): string | null {
|
||||
if (statusResponse.serverVersion === '0.0.0') {
|
||||
return 'The AliasVault server is not available. Please try again later or contact support if the problem persists.';
|
||||
}
|
||||
|
||||
if (!statusResponse.clientVersionSupported) {
|
||||
return 'This version of the AliasVault browser extension is outdated. Please update your browser extension to the latest version.';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# @version 0.12.0
|
||||
# @version 0.12.2
|
||||
|
||||
# Repository information used for downloading files and images from GitHub
|
||||
REPO_OWNER="lanedirt"
|
||||
@@ -1566,6 +1566,7 @@ handle_install_version() {
|
||||
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
|
||||
set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; }
|
||||
set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; }
|
||||
set_ip_logging || { printf "${RED}> Failed to set IP logging${NC}\n"; exit 1; }
|
||||
|
||||
# Only generate admin password if not already set
|
||||
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||
|
||||
@@ -3,6 +3,8 @@ events {
|
||||
}
|
||||
|
||||
http {
|
||||
client_max_body_size 25M;
|
||||
|
||||
upstream client {
|
||||
server client:3000;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public static class AppInfo
|
||||
/// <summary>
|
||||
/// Gets the patch version number.
|
||||
/// </summary>
|
||||
public const int VersionPatch = 0;
|
||||
public const int VersionPatch = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary of minimum supported client versions that the WebApi supports.
|
||||
|
||||
@@ -19,6 +19,9 @@ public static class ConversionUtility
|
||||
/// </summary>
|
||||
/// <param name="html">HTML input.</param>
|
||||
/// <returns>HTML with all anchor tags converted to open in a new tab when clicked on.</returns>
|
||||
/// <remarks>
|
||||
/// Note: same implementation exists in browser extension Typescript version in ConversionUtility.ts.
|
||||
/// </remarks>
|
||||
public static string ConvertAnchorTagsToOpenInNewTab(string html)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user