|
1 | 1 | package utils |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/hex" |
4 | 5 | "encoding/json" |
5 | 6 | "fmt" |
6 | 7 | "os" |
@@ -152,3 +153,120 @@ func FetchSessionStateless(username string, password string) (*Session, error) { |
152 | 153 | Settings: settings, |
153 | 154 | }, nil |
154 | 155 | } |
| 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