refactor: remove duplicated logic and parse each token once - #29
Merged
Merged
Conversation
Extract parseAnyDER for the DER parser cascade that was written out twice, and drop parseKeyData's unreachable SSH branch so unsupportedKeyError is the only place that phrases a rejection. Share the JWE part splitting and sizing between the human and --json paths, collapse printEncryptedParts' four copy-pasted blocks into a loop, and add printVerdict for the VALID/INVALID rendering the signature and claim checks had in duplicate. The verdicts stay independent; only the rendering is shared. Decode the decrypted payload once and type-switch instead of attempting an object parse and then an array parse, widening printSection to any so the array case stops re-implementing it. Add isJWT next to isJWE so token-shape dispatch has one definition per form, and give the escape helpers a fast path so unescaped output allocates nothing. In tests, delete signHS256 (a twin of signJWTWithHMAC), collapse six near-identical "unparseable key must not become an HMAC secret" tests into one table keeping every input, and move the cross-file pinTime and decodeJWTJSONMap fixtures into helpers_test.go per AGENTS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
decodeJWTHuman parsed the token, verifySignature parsed it again, and verifyClaims a third time, so a single run decoded the same segments up to four times and built eight maps to show three. parseUnverifiedJWT now returns a parsedJWT (raw string, token, segments, claims) that is threaded through the decode, signature, and claim steps; the string-taking verifySignature and verifyClaims remain as wrappers for callers that hold only the compact token. The re-parse existed because formatTimestamps rewrites claim values in place, leaving the parsed map fit only for printing. printParsedJWT now formats a maps.Clone of the claims, so the parse stays authoritative and reusable. classifyKeyArg described itself as mirroring loadKeyForKID's precedence, but a mirror is a second copy: loadKeyForKID now switches on classifyKeyArg and delegates to loadSymmetricKeyFile/loadKeyFile/loadInlineKey, so the reading the CLI reports is by construction the reading that happens. One behavior change falls out: an existing but unreadable file now reports the read error instead of falling through to a base64 attempt on its own path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
Every generateRSAKey call generated a fresh 2048-bit key, ~60 of them per run, which dominated the suite's wall time. The key is only ever read, so it is now generated once with sync.OnceValues and shared; the ten call sites that need a second key that must differ from the first ask for it explicitly via generateDistinctRSAKey. Suite time drops from ~4.2s to ~1.9s. The five key-file writers each open-coded marshal, PEM-encode, create, encode, and twelve more sites open-coded filepath.Join(t.TempDir(), …) + os.WriteFile with their own failure message. They now go through writeTempFile and writePEMFile, with writePublicKeyFile covering RSA, ECDSA, and Ed25519 alike (MarshalPKIXPublicKey handles all three) and writeJWKFile/writeJWKSetFile covering the marshal-then-write JWK sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
Seven algorithm-coverage tests repeated the same two subtest bodies — decode without a key and assert the header sections, decode with one and assert the payload — differing only in how that family's key is made. They now call assertJWEHeaderOnly/assertJWEDecrypts, and the AESKW and AESGCMKW tests merge into one table since they differed only in key size and content encryption. Key setup also moves out of the subtests, so each family builds its key once instead of once per subtest. Same 62 subtests over the same algorithms and payloads; the header-only cases now additionally assert the Encrypted Content section, which the per-family copies asserted inconsistently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
The helper's doc promised the output "once on a miss" but printed it per missing substring, so a regression dropping a whole section dumped the same output up to four times. Misses are now collected and reported together, which also names every value the section took with it in one place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
printVerdict formatted the trailing newline inside the color wrapper, so the bold attribute spanned the line break; output truncated there (piped into head, say) left the terminal with the attribute active. Fprintln puts the newline after the reset, restoring the bytes the verdict lines had before printVerdict was extracted. Also move verifySignature into helpers_test.go. Production code always holds a parsedJWT and calls printSignatureVerdict, so the string-taking wrapper had no callers outside the tests and should not sit in the package as if it were an entry point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Quality-only pass over the Go sources: reuse, simplification, efficiency, and altitude. No user-facing behavior changes except the one noted below. 624 insertions, 968 deletions.
Production code
Parse once per run.
decodeJWTHumanparsed the token,verifySignatureparsed it again, andverifyClaimsa third time, so a single--key --verify-claimsrun decoded the same segments four times and built eight maps to show three.parseUnverifiedJWTnow returns aparsedJWT(raw string, token, segments, claims) threaded through the decode, signature, and claim steps.The re-parse existed because
formatTimestampsrewrites claim values in place, leaving the parsed map fit only for printing.printParsedJWTnow formats amaps.Clone, so the parse stays authoritative and reusable.One authority for key precedence.
classifyKeyArgdescribed itself as mirroringloadKeyForKID's ladder, but a mirror is a second copy.loadKeyForKIDnow switches onclassifyKeyArgand delegates toloadSymmetricKeyFile/loadKeyFile/loadInlineKey, so the reading the CLI reports is by construction the reading that happens.Deduplication.
parseAnyDERreplaces the six-parser DER cascade that was written out verbatim twice.parseKeyData's SSH branch was unreachable — every caller discarded its error and rebuilt the message viaunsupportedKeyError, which does its own SSH detection. Removed, so the hint exists once.partSizeformatsbase64URLLen's result instead of re-decoding;jweEncryptedPartsgives both output paths one splitter.printVerdictrenders theVALID/INVALIDline for the signature and claim checks, which had it in duplicate. The two verdicts stay independent; only the rendering is shared.anyand type-switch, instead of an object parse followed by an array parse;printSectionwidened toanyso the array case stops re-implementing it.isJWTnext toisJWE, so token-shape dispatch has one definition per form.Tests
Suite time 4.2s → 1.7s.
generateRSAKeygenerated a fresh 2048-bit key ~60 times per run; it is now generated once viasync.OnceValuesand shared, withgenerateDistinctRSAKeyfor the ten sites that need a second key that must differ.assertJWEHeaderOnly/assertJWEDecrypts; same 62 subtests over the same algorithms and payloads. jwe_test.go drops 434 lines.filepath.Join(t.TempDir(), …)sites →writeTempFile/writePEMFile/writePublicKeyFile/writeJWKFile.pinTimeanddecodeJWTJSONMapmoved intohelpers_test.goper AGENTS.md.Behavior change
An existing but unreadable key file now reports
reading key file "x": permission deniedinstead of falling through to a base64 attempt on its own path and reporting "neither a valid file path nor base64-encoded data". No test pinned the old behavior; documented in AGENTS.md.Verification
gofmt,go vet, andgo test -count=1 ./...clean. Beyond the suite:printEncryptedPartscolumn alignment pinned against a literal (the existing test only checks substrings).--json, wrong-key exit 1, and the new unreadable-file error./code-reviewovermain..HEADfound no correctness bugs. It independently fuzzed theescapeFormattedJSONControlsfast path against the old rune loop over 20k random byte strings including invalid UTF-8 (byte-identical), and checked all 60generateRSAKeysites for the distinct-key requirement. Its two low findings are fixed in a66b0da.Not done
The
json.Validguard informatTimestampswas flagged as dead code by two reviewers. It is not — a test feeds hand-constructedjson.Number("1/2"),"0x10","1p2", whichbig.Rat.SetStringaccepts. Left in place with a comment explaining why.🤖 Generated with Claude Code
https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z