diff --git a/src/Horse.JWT.pas b/src/Horse.JWT.pas index 05dde96..718b88e 100644 --- a/src/Horse.JWT.pas +++ b/src/Horse.JWT.pas @@ -38,6 +38,14 @@ interface Horse.Commons; type + TSkipWhen = reference to function(const APath: string; const AMethod: TMethodType): Boolean; + TSkipRouteMethod = record + Route: string; + Method: TMethodType; + constructor Create(const ARoute: string; AMethod: TMethodType); + end; + + TSkipRouteMethods = TArray; {$IF DEFINED(FPC)} TOnResponse = {$IF DEFINED(HORSE_FPC_FUNCTIONREFERENCES)}reference to {$ENDIF}procedure(const AHorseResponse: THorseResponse; const AMessage: string; const AHTTPStatus: THTTPStatus; const APathInfo: string); {$ELSE} @@ -49,7 +57,10 @@ interface function SkipRoutes: TArray; overload; function SkipRoutes(const ARoutes: TArray): IHorseJWTConfig; overload; function SkipRoutes(const ARoute: string): IHorseJWTConfig; overload; - function Header: string; overload; + function SkipWhen: TSkipWhen; overload; + function SkipWhen(const AValue: TSkipWhen): IHorseJWTConfig; overload; + function SkipRouteMethods: TSkipRouteMethods; overload; + function SkipRouteMethods(const AValue: TSkipRouteMethods): IHorseJWTConfig; overload; function Header: string; overload; function Header(const AValue: string): IHorseJWTConfig; overload; function IsRequiredSubject: Boolean; overload; function IsRequiredSubject(const AValue: Boolean): IHorseJWTConfig; overload; @@ -75,6 +86,8 @@ THorseJWTConfig = class(TInterfacedObject, IHorseJWTConfig) private FHeader: string; FSkipRoutes: TArray; + FSkipWhen: TSkipWhen; + FSkipRouteMethods: TSkipRouteMethods; FIsRequireAudience: Boolean; FExpectedAudience: TArray; FIsRequiredExpirationTime: Boolean; @@ -86,6 +99,10 @@ THorseJWTConfig = class(TInterfacedObject, IHorseJWTConfig) function SkipRoutes: TArray; overload; function SkipRoutes(const ARoutes: TArray): IHorseJWTConfig; overload; function SkipRoutes(const ARoute: string): IHorseJWTConfig; overload; + function SkipWhen: TSkipWhen; overload; + function SkipWhen(const AValue: TSkipWhen): IHorseJWTConfig; overload; + function SkipRouteMethods: TSkipRouteMethods; overload; + function SkipRouteMethods(const AValue: TSkipRouteMethods): IHorseJWTConfig; overload; function Header: string; overload; function Header(const AValue: string): IHorseJWTConfig; overload; function IsRequiredSubject: Boolean; overload; @@ -124,6 +141,12 @@ implementation INVALID_AUTHORIZATION_TYPE = 'Invalid authorization type'; UNAUTHORIZED = 'Unauthorized'; +constructor TSkipRouteMethod.Create(const ARoute: string; AMethod: TMethodType); +begin + Route := ARoute; + Method := AMethod; +end; + procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseResponse; ANext: {$IF DEFINED(FPC)}TNextProc{$ELSE}TProc{$ENDIF}; const ASecretJWT: string; const AConfig: IHorseJWTConfig); var {$IF DEFINED(FPC)} @@ -190,6 +213,26 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons LPathInfo := AHorseRequest.RawWebRequest.PathInfo; if LPathInfo = EmptyStr then LPathInfo := '/'; + + for var I := 0 to High(LConfig.SkipRouteMethods) do + begin + if MatchRoute(LPathInfo, [LConfig.SkipRouteMethods[I].Route]) and + (AHorseRequest.MethodType = LConfig.SkipRouteMethods[I].Method) then + begin + ANext(); + Exit; + end; + end; + + if Assigned(LConfig.SkipWhen) then + begin + if LConfig.SkipWhen()(LPathInfo,AHorseRequest.MethodType) then + begin + ANext(); + Exit; + end; + end; + if MatchRoute(LPathInfo, LConfig.SkipRoutes) then begin ANext(); @@ -210,7 +253,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons LHeaderNormalize, LToken) then begin if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, TOKEN_NOT_FOUND, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, TOKEN_NOT_FOUND, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send(TOKEN_NOT_FOUND).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create(TOKEN_NOT_FOUND); @@ -219,7 +262,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons if Pos('bearer', LowerCase(LToken)) = 0 then begin if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, INVALID_AUTHORIZATION_TYPE, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, INVALID_AUTHORIZATION_TYPE, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send(INVALID_AUTHORIZATION_TYPE).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create(INVALID_AUTHORIZATION_TYPE); @@ -248,7 +291,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons LJWT := TJOSEContext.Create(LToken, TJWTClaims); except if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send(UNAUTHORIZED).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create(UNAUTHORIZED); @@ -258,7 +301,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons if LJWT.GetJOSEObject = nil then begin if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send(UNAUTHORIZED).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create(UNAUTHORIZED); @@ -333,7 +376,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons on E: Exception do begin if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, UNAUTHORIZED, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send(UNAUTHORIZED).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create(UNAUTHORIZED); @@ -348,7 +391,7 @@ procedure Middleware(AHorseRequest: THorseRequest; AHorseResponse: THorseRespons on E: Exception do begin if Assigned(LConfig.OnResponse()) then - LConfig.OnResponse()(AHorseResponse, 'Invalid token authorization. ' + E.Message, THTTPStatus.Unauthorized,LPathInfo) + LConfig.OnResponse()(AHorseResponse, 'Invalid token authorization. ' + E.Message, THTTPStatus.Unauthorized, LPathInfo) else AHorseResponse.Send('Invalid token authorization. ' + E.Message).Status(THTTPStatus.Unauthorized); raise EHorseCallbackInterrupted.Create; @@ -503,6 +546,38 @@ function THorseJWTConfig.SkipRoutes(const ARoute: string): IHorseJWTConfig; Result := SkipRoutes([ARoute]); end; +function THorseJWTConfig.SkipWhen: TSkipWhen; +begin + Result := FSkipWhen; +end; + +function THorseJWTConfig.SkipWhen(const AValue: TSkipWhen): IHorseJWTConfig; +begin + FSkipWhen := AValue; + Result := Self; +end; + +function THorseJWTConfig.SkipRouteMethods: TSkipRouteMethods; +begin + Result := FSkipRouteMethods; +end; + +function THorseJWTConfig.SkipRouteMethods( + const AValue: TSkipRouteMethods): IHorseJWTConfig; +var + I: Integer; +begin + FSkipRouteMethods := AValue; + + // نرمال‌سازی مثل SkipRoutes + for I := 0 to High(FSkipRouteMethods) do + if (FSkipRouteMethods[I].Route <> '') and + (FSkipRouteMethods[I].Route[1] <> '/') then + FSkipRouteMethods[I].Route := '/' + FSkipRouteMethods[I].Route; + + Result := Self; +end; + constructor THorseJWTConfig.Create; begin FHeader := 'authorization'; @@ -529,4 +604,4 @@ function THorseJWTConfig.OnResponse(const AValue: TOnResponse): IHorseJWTConfig; Result := Self; end; -end. \ No newline at end of file +end.