Skip to content

Commit dec2098

Browse files
committed
Added a reset-password command
1 parent b709a98 commit dec2098

2 files changed

Lines changed: 194 additions & 1 deletion

File tree

main.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,81 @@ func main() {
169169
},
170170
}
171171

172+
// --- RESET PASSWORD ---
173+
var resetPasswordCmd = &cobra.Command{
174+
Use: "reset-password",
175+
Aliases: []string{"reset-pass", "change-password", "change-pass"},
176+
Short: "Change your vault password",
177+
Args: cobra.NoArgs,
178+
Run: func(cmd *cobra.Command, args []string) {
179+
// Check if the config file exists BEFORE starting
180+
_, err := os.Stat("zephyrus.conf")
181+
isPersistent := err == nil
182+
183+
session, err := getEffectiveSession()
184+
if err != nil {
185+
fmt.Printf("❌ Authentication failed: %v\n", err)
186+
return
187+
}
188+
189+
// Confirm current password
190+
fmt.Println("For security, please confirm your current vault password.")
191+
currentPass, err := utils.GetPassword("Current Vault Password: ")
192+
if err != nil {
193+
fmt.Printf("❌ Error reading password: %v\n", err)
194+
return
195+
}
196+
197+
if currentPass != session.Password {
198+
fmt.Println("❌ Current password is incorrect.")
199+
return
200+
}
201+
202+
// Get new password
203+
fmt.Println("\nCreate a new vault password.")
204+
fmt.Println("⚠️ IMPORTANT: This password cannot be recovered. Please remember it!")
205+
newPass, err := utils.GetPassword("New Vault Password: ")
206+
if err != nil {
207+
fmt.Printf("❌ Error reading password: %v\n", err)
208+
return
209+
}
210+
211+
if newPass == "" {
212+
fmt.Println("❌ New password cannot be empty.")
213+
return
214+
}
215+
216+
// Confirm new password
217+
passConfirm, err := utils.GetPassword("Confirm New Vault Password: ")
218+
if err != nil {
219+
fmt.Printf("❌ Error reading password: %v\n", err)
220+
return
221+
}
222+
223+
if newPass != passConfirm {
224+
fmt.Println("❌ New passwords do not match.")
225+
return
226+
}
227+
228+
fmt.Println("\nResetting vault password...")
229+
230+
// Reset the password
231+
err = utils.ResetPassword(session, newPass)
232+
if err != nil {
233+
fmt.Printf("❌ Password reset failed: %v\n", err)
234+
return
235+
}
236+
237+
// Only save the updated session if we were already in a persistent session
238+
if isPersistent {
239+
session.Save()
240+
}
241+
242+
fmt.Println("✔ Password reset successful!")
243+
fmt.Println("Your vault has been re-encrypted with the new password.")
244+
},
245+
}
246+
172247
// --- CONNECT ---
173248
var connectCmd = &cobra.Command{
174249
Use: "connect [username]",
@@ -877,7 +952,7 @@ Examples:
877952
}
878953

879954
rootCmd.AddCommand(
880-
setupCmd, connectCmd, disconnectCmd,
955+
setupCmd, connectCmd, resetPasswordCmd, disconnectCmd,
881956
uploadCmd, downloadCmd, deleteCmd,
882957
listCmd, searchCmd, purgeCmd, shareCmd, readCmd, sharedCmd, settingsCmd, infoCmd,
883958
locallsCmd, localdirCmd,

utils/auth.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package utils
22

33
import (
4+
"encoding/hex"
45
"encoding/json"
56
"fmt"
67
"os"
@@ -152,3 +153,120 @@ func FetchSessionStateless(username string, password string) (*Session, error) {
152153
Settings: settings,
153154
}, nil
154155
}
156+
157+
// ResetPassword changes the vault password and re-encrypts all protected data
158+
func ResetPassword(session *Session, newPassword string) error {
159+
repoURL := fmt.Sprintf("git@github.com:%s/.zephyrus.git", session.Username)
160+
161+
PrintProgressStep(1, 5, "Validating new password...")
162+
if newPassword == "" {
163+
return fmt.Errorf("new password cannot be empty")
164+
}
165+
PrintCompletionLine("Password validated")
166+
167+
// Re-encrypt master key with new password
168+
PrintProgressStep(2, 5, "Re-encrypting master key...")
169+
newMasterKeyEncrypted, err := Encrypt(session.RawKey, newPassword)
170+
if err != nil {
171+
return fmt.Errorf("failed to encrypt master key: %w", err)
172+
}
173+
PrintCompletionLine("Master key re-encrypted")
174+
175+
// Re-encrypt index with new password
176+
// First, we need to update all file keys in the index
177+
PrintProgressStep(3, 5, "Re-encrypting vault index...")
178+
err = updateIndexFileKeysForPassword(session.Index, session.Password, newPassword)
179+
if err != nil {
180+
return fmt.Errorf("failed to update file keys: %w", err)
181+
}
182+
183+
indexBytes, err := session.Index.ToBytes(newPassword)
184+
if err != nil {
185+
return fmt.Errorf("failed to encrypt index: %w", err)
186+
}
187+
PrintCompletionLine("Vault index re-encrypted")
188+
189+
// Re-encrypt settings with new password
190+
PrintProgressStep(4, 5, "Re-encrypting settings...")
191+
settingsBytes, err := session.Settings.ToBytes(newPassword)
192+
if err != nil {
193+
return fmt.Errorf("failed to encrypt settings: %w", err)
194+
}
195+
PrintCompletionLine("Settings re-encrypted")
196+
197+
// Re-encrypt shared index with new password
198+
sharedIndexEncrypted, err := session.SharedIndex.EncryptForRemote(newPassword)
199+
if err != nil {
200+
return fmt.Errorf("failed to encrypt shared index: %w", err)
201+
}
202+
203+
// Push all re-encrypted files to GitHub
204+
PrintProgressStep(5, 5, "Pushing updated files to GitHub...")
205+
filesToPush := map[string][]byte{
206+
".config/key": newMasterKeyEncrypted,
207+
".config/index": indexBytes,
208+
".config/settings": settingsBytes,
209+
"shared/.config/index": sharedIndexEncrypted,
210+
}
211+
212+
err = PushFilesWithAuthor(repoURL, session.RawKey, filesToPush, session.Settings.CommitMessage, session.Settings.CommitAuthorName, session.Settings.CommitAuthorEmail)
213+
if err != nil {
214+
return fmt.Errorf("failed to push updated files: %w", err)
215+
}
216+
PrintCompletionLine("Files pushed to GitHub")
217+
218+
// Update session password and save locally if persistent
219+
session.Password = newPassword
220+
session.Save()
221+
222+
return nil
223+
}
224+
225+
// updateIndexFileKeysForPassword recursively updates all file key encryption in the index
226+
func updateIndexFileKeysForPassword(vi VaultIndex, oldPassword string, newPassword string) error {
227+
return updateIndexTreeFileKeys(vi, oldPassword, newPassword)
228+
}
229+
230+
// updateIndexTreeFileKeys recursively walks the index and updates file key encryption
231+
func updateIndexTreeFileKeys(entries VaultIndex, oldPassword string, newPassword string) error {
232+
for name, entry := range entries {
233+
if entry.Type == "file" {
234+
// Decrypt file key with old password
235+
encryptedKey, err := DecryptHexString(entry.FileKey, oldPassword)
236+
if err != nil {
237+
return fmt.Errorf("failed to decrypt file key: %w", err)
238+
}
239+
240+
// Re-encrypt with new password
241+
newEncryptedKey, err := Encrypt(encryptedKey, newPassword)
242+
if err != nil {
243+
return fmt.Errorf("failed to re-encrypt file key: %w", err)
244+
}
245+
246+
// Update the entry with hex-encoded new encrypted key
247+
entry.FileKey = HexEncodeBytes(newEncryptedKey)
248+
entries[name] = entry // Write back to map
249+
} else if entry.Type == "folder" && entry.Contents != nil {
250+
// Recurse into subdirectories
251+
err := updateIndexTreeFileKeys(entry.Contents, oldPassword, newPassword)
252+
if err != nil {
253+
return err
254+
}
255+
}
256+
}
257+
return nil
258+
}
259+
260+
// DecryptHexString decrypts a hex-encoded encrypted string
261+
func DecryptHexString(hexStr string, password string) ([]byte, error) {
262+
encryptedData, err := hex.DecodeString(hexStr)
263+
if err != nil {
264+
return nil, err
265+
}
266+
return Decrypt(encryptedData, password)
267+
}
268+
269+
// HexEncodeBytes encodes bytes as a hex string
270+
func HexEncodeBytes(data []byte) string {
271+
return hex.EncodeToString(data)
272+
}

0 commit comments

Comments
 (0)