deriveClaimCodeForBucket is the inner derive — split out so VerifyClaimCode can derive both the current and previous bucket without re-computing the bucket math twice.
(secret []byte, userID, workspaceID string, bucket int64)
| 84 | // VerifyClaimCode can derive both the current and previous bucket |
| 85 | // without re-computing the bucket math twice. |
| 86 | func deriveClaimCodeForBucket(secret []byte, userID, workspaceID string, bucket int64) string { |
| 87 | mac := hmac.New(sha256.New, secret) |
| 88 | // Length-prefix each component so two distinct (user, workspace) |
| 89 | // pairs that happen to concatenate to the same byte string (e.g. |
| 90 | // userID="abc" + workspaceID="def" vs. userID="abcdef" + |
| 91 | // workspaceID="") can't collide on the same code. Belt-and- |
| 92 | // suspenders — UUIDs don't realistically alias, but a future |
| 93 | // schema change to numeric IDs could re-introduce the risk. |
| 94 | writeLenPrefixed(mac, []byte(userID)) |
| 95 | writeLenPrefixed(mac, []byte(workspaceID)) |
| 96 | var bucketBytes [8]byte |
| 97 | binary.BigEndian.PutUint64(bucketBytes[:], uint64(bucket)) |
| 98 | mac.Write(bucketBytes[:]) |
| 99 | sum := mac.Sum(nil) |
| 100 | // Take the first 8 bytes as an unsigned int, then mod down to |
| 101 | // the digit count. Using the full 32-byte hash would be wasteful |
| 102 | // (we throw away 24 bytes either way); 8 bytes gives plenty of |
| 103 | // entropy before truncation. |
| 104 | n := binary.BigEndian.Uint64(sum[:8]) % claimCodeModulus |
| 105 | return fmt.Sprintf("%0*d", claimCodeDigits, n) |
| 106 | } |
| 107 | |
| 108 | // writeLenPrefixed writes a 4-byte big-endian length followed by the |
| 109 | // raw bytes. Cheap collision-resistant separator for HMAC inputs. |
no test coverage detected