Sign returns a URL-safe token signed with `secret`. action must be ActionApprove or ActionReject. exp sets the token's expiration — callers should pass a value slightly after the message's approval_expires_at so a click received just before TTL still works.
(secret, messageID, action string, exp time.Time)
| 58 | // callers should pass a value slightly after the message's |
| 59 | // approval_expires_at so a click received just before TTL still works. |
| 60 | func Sign(secret, messageID, action string, exp time.Time) (string, error) { |
| 61 | if action != ActionApprove && action != ActionReject { |
| 62 | return "", fmt.Errorf("invalid action %q", action) |
| 63 | } |
| 64 | // The payload uses '|' as a separator and '\n' would confuse log |
| 65 | // scanners. Defend by rejecting up-front; message IDs are generated |
| 66 | // with hex characters only, so legitimate callers never hit this. |
| 67 | if strings.ContainsAny(messageID, "|\n") { |
| 68 | return "", fmt.Errorf("messageID contains reserved characters") |
| 69 | } |
| 70 | payload := fmt.Sprintf("%s|%s|%d", messageID, action, exp.Unix()) |
| 71 | sig := signMAC([]byte(secret), []byte(payload)) |
| 72 | return base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." + |
| 73 | base64.RawURLEncoding.EncodeToString(sig), nil |
| 74 | } |
| 75 | |
| 76 | // Verify parses, HMAC-checks (against any of `secrets`), and exp-checks |
| 77 | // a token. Returns the claims on success; ErrInvalidToken for |