@@ -604,6 +604,7 @@ export type ZodStringCheck =
604604 | { kind : "trim" ; message ?: string }
605605 | { kind : "toLowerCase" ; message ?: string }
606606 | { kind : "toUpperCase" ; message ?: string }
607+ | { kind : "jwt" ; alg ?: string ; message ?: string }
607608 | {
608609 kind : "datetime" ;
609610 offset : boolean ;
@@ -641,6 +642,7 @@ const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
641642const uuidRegex =
642643 / ^ [ 0 - 9 a - f A - F ] { 8 } \b - [ 0 - 9 a - f A - F ] { 4 } \b - [ 0 - 9 a - f A - F ] { 4 } \b - [ 0 - 9 a - f A - F ] { 4 } \b - [ 0 - 9 a - f A - F ] { 12 } $ / i;
643644const nanoidRegex = / ^ [ a - z 0 - 9 _ - ] { 21 } $ / i;
645+ const jwtRegex = / ^ [ A - Z a - z 0 - 9 - _ ] + \. [ A - Z a - z 0 - 9 - _ ] + \. [ A - Z a - z 0 - 9 - _ ] * $ / ;
644646const durationRegex =
645647 / ^ [ - + ] ? P (? ! $ ) (?: (?: [ - + ] ? \d + Y ) | (?: [ - + ] ? \d + [ . , ] \d + Y $ ) ) ? (?: (?: [ - + ] ? \d + M ) | (?: [ - + ] ? \d + [ . , ] \d + M $ ) ) ? (?: (?: [ - + ] ? \d + W ) | (?: [ - + ] ? \d + [ . , ] \d + W $ ) ) ? (?: (?: [ - + ] ? \d + D ) | (?: [ - + ] ? \d + [ . , ] \d + D $ ) ) ? (?: T (? = [ \d + - ] ) (?: (?: [ - + ] ? \d + H ) | (?: [ - + ] ? \d + [ . , ] \d + H $ ) ) ? (?: (?: [ - + ] ? \d + M ) | (?: [ - + ] ? \d + [ . , ] \d + M $ ) ) ? (?: [ - + ] ? \d + (?: [ . , ] \d + ) ? S ) ? ) ? ?$ / ;
646648
@@ -739,6 +741,25 @@ function isValidIP(ip: string, version?: IpVersion) {
739741 return false ;
740742}
741743
744+ function isValidJWT ( jwt : string , alg ?: string ) : boolean {
745+ if ( ! jwtRegex . test ( jwt ) ) return false ;
746+ try {
747+ const [ header ] = jwt . split ( "." ) ;
748+ // Convert base64url to base64
749+ const base64 = header
750+ . replace ( / - / g, "+" )
751+ . replace ( / _ / g, "/" )
752+ . padEnd ( header . length + ( ( 4 - ( header . length % 4 ) ) % 4 ) , "=" ) ;
753+ const decoded = JSON . parse ( atob ( base64 ) ) ;
754+ if ( typeof decoded !== "object" || decoded === null ) return false ;
755+ if ( ! decoded . typ || ! decoded . alg ) return false ;
756+ if ( alg && decoded . alg !== alg ) return false ;
757+ return true ;
758+ } catch {
759+ return false ;
760+ }
761+ }
762+
742763function isValidCidr ( ip : string , version ?: IpVersion ) {
743764 if ( ( version === "v4" || ! version ) && ipv4CidrRegex . test ( ip ) ) {
744765 return true ;
@@ -1012,6 +1033,16 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
10121033 } ) ;
10131034 status . dirty ( ) ;
10141035 }
1036+ } else if ( check . kind === "jwt" ) {
1037+ if ( ! isValidJWT ( input . data , check . alg ) ) {
1038+ ctx = this . _getOrReturnCtx ( input , ctx ) ;
1039+ addIssueToContext ( ctx , {
1040+ validation : "jwt" ,
1041+ code : ZodIssueCode . invalid_string ,
1042+ message : check . message ,
1043+ } ) ;
1044+ status . dirty ( ) ;
1045+ }
10151046 } else if ( check . kind === "cidr" ) {
10161047 if ( ! isValidCidr ( input . data , check . version ) ) {
10171048 ctx = this . _getOrReturnCtx ( input , ctx ) ;
@@ -1105,6 +1136,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
11051136 return this . _addCheck ( { kind : "base64url" , ...errorUtil . errToObj ( message ) } ) ;
11061137 }
11071138
1139+ jwt ( options ?: { alg ?: string ; message ?: string } ) {
1140+ return this . _addCheck ( { kind : "jwt" , ...errorUtil . errToObj ( options ) } ) ;
1141+ }
1142+
11081143 ip ( options ?: string | { version ?: IpVersion ; message ?: string } ) {
11091144 return this . _addCheck ( { kind : "ip" , ...errorUtil . errToObj ( options ) } ) ;
11101145 }
0 commit comments