Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ mach2 = "0.6.0"
[dev-dependencies]
insta = "1"
mockall = "0.13"
parking_lot = "0.12"
tempfile = "3"
anyhow = "1"

Expand Down
20 changes: 16 additions & 4 deletions README-ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ ok

首次运行时创建 vault,设置强主密码,并把恢复词保存在安全位置。如果主密码和恢复词都丢失,维护者无法帮你恢复密码库。

## 基本使用

oak-keyring 会打开一个全屏终端界面。主要使用流程是:

1. **创建或解锁 vault** — 运行 `ok`,首次使用时创建本地 vault,之后用主密码解锁已有 vault。
2. **浏览和搜索记录** — 通过侧边栏和记录列表浏览凭证、安全笔记、标签和回收站。使用 `Ctrl+K` 进入搜索模式,`Enter` 保留过滤结果,`Esc` 取消搜索。
3. **查看和复制秘密** — 选择记录后在详情面板查看字段。详情面板中,`c` 复制密码字段,`u` 复制用户名字段,`p` 在可用时显示或隐藏密码字段。
4. **创建和编辑记录** — 在非回收站状态下,`n` 创建新记录,`e` 编辑当前选中记录。
5. **生成密码** — 在主界面用 `Ctrl+G` 打开密码生成器;在记录表单中出现生成器时也可以直接使用。
6. **配置同步** — 用 `Ctrl+P` 打开 Config。Google Drive 同步是可选功能,并且仍属于预览版边界;配置完成后,可在主界面用 `Ctrl+R` 触发同步。
7. **导入和导出** — 使用 TUI 中的导入/导出流程迁移数据。导出的文件应按敏感数据处理。

当前网站文档见
[openkeyring.com/zh/docs/](https://openkeyring.com/zh/docs/)。

## 预览版状态

oak-keyring 仍处于 pre-1.0 预览阶段(v0.8.0-preview.1)。
Expand All @@ -98,11 +113,8 @@ oak-keyring 是 local-first:vault 属于用户,默认保存在本机。正

## 链接

- [网站文档](https://openkeyring.com/zh/docs/) — 安装、使用、快捷键、安全和预览版状态
- [SECURITY.md](SECURITY.md) — 漏洞报告和安全边界
- [CONTRIBUTING.md](CONTRIBUTING.md) — 如何贡献
- [CHANGELOG.md](CHANGELOG.md) — 发布历史
- [LICENSE](LICENSE) — MIT license

## 鸣谢

感谢 [linux.do](https://linux.do) 社区提供讨论、反馈和支持。
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,31 @@ On first run, create a vault, choose a strong master password, and save the
recovery words somewhere safe. If both the master password and recovery words
are lost, maintainers cannot recover your vault.

## Basic Usage

oak-keyring opens into a full-screen terminal interface. The main workflow is:

1. **Create or unlock a vault** — start `ok`, then create a local vault on
first run or unlock an existing vault with your master password.
2. **Browse and search records** — use the sidebar and record list to move
through credentials, secure notes, tags, and trash. Use `Ctrl+K` to enter
search mode, then `Enter` to keep the filtered result or `Esc` to cancel.
3. **View and copy secrets** — select a record to inspect its details. In the
detail panel, use `c` to copy the password field, `u` to copy the username
field, and `p` to reveal or hide password fields when available.
4. **Create and edit records** — use `n` to create a new record and `e` to edit
the selected record outside trash.
5. **Generate passwords** — open the password generator from the main screen
with `Ctrl+G`, or use the generator when it appears inside record forms.
6. **Configure sync** — open Config with `Ctrl+P`. Google Drive sync is
optional and still part of the preview boundary; after it is configured,
`Ctrl+R` triggers sync from the main screen.
7. **Import and export** — use the TUI import/export flows when moving data
into or out of the vault. Treat exported files as sensitive data.

For the current website documentation, see
[openkeyring.com/en/docs/](https://openkeyring.com/en/docs/).

## Community and Support

Welcome to the OpenKeyring community. If you need help, have questions, or
Expand Down Expand Up @@ -130,11 +155,9 @@ binary. Report security issues through [SECURITY.md](SECURITY.md).

## Links

- [Website Docs](https://openkeyring.com/en/docs/) — install, usage,
shortcuts, security, and preview status
- [SECURITY.md](SECURITY.md) — vulnerability reporting and security boundaries
- [CONTRIBUTING.md](CONTRIBUTING.md) — how to contribute
- [CHANGELOG.md](CHANGELOG.md) — release history
- [LICENSE](LICENSE) — MIT license

## Acknowledgements

Thanks to the [linux.do](https://linux.do) community for the discussion, feedback, and support.
1 change: 1 addition & 0 deletions src/tui/components/generator_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ mod tests {

#[test]
fn render_embedded_has_no_style_selector() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let state = GeneratorState::new();
let lines = render_generator_panel(&state, true, 56, true);
let style_label = t!("tui.generator.style_label").to_string();
Expand Down
4 changes: 4 additions & 0 deletions src/tui/components/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ mod tests {

#[test]
fn render_text_input_shows_label() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let lines = render_text_input("name", "GitHub", false, false, true, false, 60);
assert_eq!(lines.len(), 1);
}
Expand All @@ -298,13 +299,15 @@ mod tests {

#[test]
fn render_masked_input_shows_dots() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let lines = render_text_input("pass", "secret", false, false, true, true, 60);
let text: String = lines[0].spans.iter().map(|s| s.content.as_ref()).collect();
assert!(text.contains("\u{2022}\u{2022}\u{2022}\u{2022}\u{2022}\u{2022}"));
}

#[test]
fn render_password_input_visible_shows_plaintext() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let buttons = [
PasswordButton {
label: "显示".to_string(),
Expand All @@ -324,6 +327,7 @@ mod tests {

#[test]
fn render_password_input_masked_shows_bullets() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let buttons = [PasswordButton {
label: "显示".to_string(),
focus_variant: PasswordFieldFocus::Show,
Expand Down
10 changes: 6 additions & 4 deletions src/tui/i18n/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,22 @@ pub fn switch_locale(locale: &str) {
}

#[cfg(test)]
static LOCALE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
static LOCALE_LOCK: parking_lot::ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());

/// RAII guard that serializes locale-dependent tests and restores locale on drop.
/// Uses a process-wide mutex so no two locale-sensitive tests run concurrently.
/// Uses a process-wide reentrant mutex so no two locale-sensitive tests run
/// concurrently, while still allowing a single test to nest guards (e.g. an
/// outer manual guard plus an inner render-helper guard) without deadlocking.
#[cfg(test)]
pub struct LocaleGuard {
original: String,
_lock: std::sync::MutexGuard<'static, ()>,
_lock: parking_lot::ReentrantMutexGuard<'static, ()>,
}

#[cfg(test)]
impl LocaleGuard {
pub fn new(locale: &str) -> Self {
let lock = LOCALE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let lock = LOCALE_LOCK.lock();
let original = rust_i18n::locale().to_string();
init(locale);
Self {
Expand Down
1 change: 1 addition & 0 deletions src/tui/screens/database_recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,7 @@ mod tests {

#[test]
fn enter_on_okb_without_path_sets_error() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let mut screen = DatabaseRecoveryScreen::new(DatabaseRecoveryOrigin::StartupKeyOnly);
screen.focus = DatabaseRecoveryFocus::Okb;
screen.mode = DatabaseRecoveryMode::OkbPathInput;
Expand Down
2 changes: 2 additions & 0 deletions src/tui/screens/form/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ mod tests {

#[test]
fn weak_password_dialog_renders_actions_and_newlook_warning_icon() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
Expand All @@ -955,6 +956,7 @@ mod tests {

#[test]
fn unfocused_notes_textarea_does_not_render_cursor_style() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let mut state = FormState::new_edit(uuid::Uuid::nil(), CredentialType::Login);
state.fields.name = "Example".into();
state.fields.set_notes_text("internal notes");
Expand Down
1 change: 1 addition & 0 deletions src/tui/screens/key_recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ mod tests {

#[test]
fn confirm_with_incomplete_words_sets_inline_error() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let mut screen = KeyRecoveryScreen::new(KeyRecoveryOrigin::OnboardingRestore);
screen.focus = KeyRecoveryFocus::Confirm;
let result = screen.handle_key_for_test(key(KeyCode::Enter));
Expand Down
1 change: 1 addition & 0 deletions src/tui/screens/main/detail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1974,6 +1974,7 @@ mod tests {

#[test]
fn render_trash_detail_empty() {
let _guard = crate::tui::i18n::LocaleGuard::en();
let mut state = DetailPanelState::default();
state.set_trash_context(true, 30);
let result = render_detail_snapshot(&state, 60, 20, true, true);
Expand Down
Loading
Loading