mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-04 08:28:43 +00:00
feat(portable): add secure Windows portable release (#15)
Implement marker-based portable storage, portable WebView and log paths, secure queue and migration sanitization, and Windows portable ZIP validation while preserving the NSIS installer path. Refs #15
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PlatformInfo = { os: string, arch: string, targetTriple: string, };
|
||||
export type PlatformInfo = { os: string, arch: string, targetTriple: string, portable: boolean, };
|
||||
|
||||
@@ -17,14 +17,20 @@ export const KeychainPermissionModal: React.FC = () => {
|
||||
|
||||
const isMac = platform.os === 'macos';
|
||||
const storeName =
|
||||
platform.os === 'windows'
|
||||
platform.portable
|
||||
? 'the portable Firelink data folder'
|
||||
: platform.os === 'windows'
|
||||
? 'Windows Credential Manager'
|
||||
: platform.os === 'linux'
|
||||
? 'your Linux credential store'
|
||||
: platform.os === 'macos'
|
||||
? 'macOS Keychain'
|
||||
: "this system's credential store";
|
||||
const grantLabel = isMac ? 'Grant Access' : 'Enable Secure Storage';
|
||||
const grantLabel = platform.portable
|
||||
? 'Enable Portable Pairing'
|
||||
: isMac
|
||||
? 'Grant Access'
|
||||
: 'Enable Secure Storage';
|
||||
|
||||
const handleGrant = async () => {
|
||||
setIsGranting(true);
|
||||
@@ -75,14 +81,18 @@ export const KeychainPermissionModal: React.FC = () => {
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{isMac
|
||||
{platform.portable
|
||||
? 'The pairing token is portable with this folder. Treat the folder as sensitive and do not share it.'
|
||||
: isMac
|
||||
? 'macOS may show a Keychain prompt after you grant access.'
|
||||
: 'This usually completes silently. If the credential service is unavailable, Firelink will show the error here and the extension will stay paired for this session only.'}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Note:</strong> Firelink only writes its own dedicated credential entry. It cannot access other
|
||||
saved passwords or credential items on your system.
|
||||
<strong>Note:</strong>{' '}
|
||||
{platform.portable
|
||||
? 'Portable mode stores only the pairing token in this folder. It does not copy site passwords or browser credentials.'
|
||||
: 'Firelink only writes its own dedicated credential entry. It cannot access other saved passwords or credential items on your system.'}
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
@@ -93,8 +103,11 @@ export const KeychainPermissionModal: React.FC = () => {
|
||||
)}
|
||||
|
||||
<div className="bg-bg-modal-accent p-3 rounded-lg border border-border-modal text-xs">
|
||||
<strong>Hint:</strong> If you select Later, the extension will only work for this session.
|
||||
You can enable secure storage anytime from <strong>Settings > Integrations</strong>.
|
||||
<strong>Hint:</strong>{' '}
|
||||
{platform.portable
|
||||
? 'The portable pairing token is already stored with this folder; you can enable it here or select Later.'
|
||||
: 'If you select Later, the extension will only work for this session.'}
|
||||
You can enable storage anytime from <strong>Settings > Integrations</strong>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1214,9 +1214,13 @@ className="app-button px-3 py-1.5 text-[12px] flex items-center gap-1.5 disabled
|
||||
<Check size={16} strokeWidth={2.5} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold text-green-500 m-0">Credential Storage Available</h4>
|
||||
<h4 className="text-sm font-semibold text-green-500 m-0">
|
||||
{platform.portable ? 'Portable Pairing Enabled' : 'Credential Storage Available'}
|
||||
</h4>
|
||||
<p className="text-xs text-text-secondary m-0 mt-0.5">
|
||||
Your pairing token is securely saved in this system's credential store and will persist across restarts.
|
||||
{platform.portable
|
||||
? 'Your pairing token is stored with this portable Firelink folder and will persist when the folder is moved. Treat the folder as sensitive.'
|
||||
: "Your pairing token is securely saved in this system's credential store and will persist across restarts."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1224,16 +1228,19 @@ className="app-button px-3 py-1.5 text-[12px] flex items-center gap-1.5 disabled
|
||||
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 flex items-start gap-3">
|
||||
<ShieldAlert className="w-5 h-5 text-orange-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold text-text-primary mb-1">Credential Storage Needed</h4>
|
||||
<h4 className="text-sm font-semibold text-text-primary mb-1">
|
||||
{platform.portable ? 'Portable Pairing Available' : 'Credential Storage Needed'}
|
||||
</h4>
|
||||
<p className="text-xs text-text-secondary mb-3">
|
||||
Firelink needs access to this system's credential store to securely save your pairing token across app restarts.
|
||||
Currently, your extension will only stay connected for this session.
|
||||
{platform.portable
|
||||
? 'Your pairing token is stored with this portable Firelink folder and will persist across restarts. Enable it here to review the portable-storage warning.'
|
||||
: "Firelink needs access to this system's credential store to securely save your pairing token across app restarts. Currently, your extension will only stay connected for this session."}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => settings.setShowKeychainModal(true)}
|
||||
className="px-4 py-1.5 rounded-md text-xs font-medium transition-colors bg-accent text-white hover:bg-accent/90 shadow-sm"
|
||||
>
|
||||
Grant Credential Access
|
||||
{platform.portable ? 'Review Portable Pairing' : 'Grant Credential Access'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,9 @@ const tauriStorage: StateStorage = {
|
||||
* Keychain identifier for the browser-extension pairing token. The token is an
|
||||
* HMAC shared secret and is therefore persisted via the OS keychain rather
|
||||
* than the user-data database. Legacy plaintext values are migrated into the
|
||||
* Keychain before being removed from persisted settings.
|
||||
* Keychain before being removed from persisted settings. Portable mode is the
|
||||
* explicit exception: its pairing token is persisted with the portable folder
|
||||
* so extension pairing follows that folder.
|
||||
*/
|
||||
const PAIRING_TOKEN_KEYCHAIN_ID = 'extension-pairing-token';
|
||||
|
||||
|
||||
@@ -121,11 +121,10 @@ const DOWNLOAD_SECRET_FIELDS = ['password', 'cookies', 'headers'] as const;
|
||||
* progress fields (`fraction`, `speed`, `eta`) are also dropped as in the
|
||||
* existing persistence path.
|
||||
*
|
||||
* Note: `url` is intentionally retained even though it may contain signed
|
||||
* query parameters — redacting it would break resume/retry since the URL is
|
||||
* the download source. Ad-hoc credentials entered in the Add Downloads modal
|
||||
* are therefore session-scoped; site-login passwords (Keychain-backed) are
|
||||
* unaffected by this redaction.
|
||||
* Note: standard persistence intentionally retains `url` because it is the
|
||||
* download source. The backend applies a stricter portable-mode policy: URL
|
||||
* userinfo, query, and fragment components are removed before portable data
|
||||
* is written, and affected active records are not auto-resumed.
|
||||
*/
|
||||
export const redactDownloadForPersistence = (item: DownloadItem): DownloadItem => {
|
||||
const copy: DownloadItem = { ...item };
|
||||
|
||||
@@ -5,7 +5,8 @@ import { invokeCommand as invoke } from '../ipc';
|
||||
const fallback: PlatformInfo = {
|
||||
os: 'unknown',
|
||||
arch: 'unknown',
|
||||
targetTriple: 'unknown'
|
||||
targetTriple: 'unknown',
|
||||
portable: false
|
||||
};
|
||||
|
||||
let cached: PlatformInfo | null = null;
|
||||
|
||||
Reference in New Issue
Block a user