pax_global_header00006660000000000000000000000064144540077610014522gustar00rootroot0000000000000052 comment=0e7245cd974ed8872ecd7de86e11af5aa9859dea golang-github-appleboy-gin-jwt-2.9.1/000077500000000000000000000000001445400776100174505ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/.editorconfig000066400000000000000000000016311445400776100221260ustar00rootroot00000000000000# unifying the coding style for different editors and IDEs => editorconfig.org ; indicate this is the root of the project root = true ########################################################### ; common ########################################################### [*] charset = utf-8 end_of_line = LF insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 ########################################################### ; make ########################################################### [Makefile] indent_style = tab [makefile] indent_style = tab ########################################################### ; markdown ########################################################### [*.md] trim_trailing_whitespace = false ########################################################### ; golang ########################################################### [*.go] indent_style = tabgolang-github-appleboy-gin-jwt-2.9.1/.github/000077500000000000000000000000001445400776100210105ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/.github/dependabot.yml000066400000000000000000000003031445400776100236340ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: gomod directory: / schedule: interval: weekly golang-github-appleboy-gin-jwt-2.9.1/.github/workflows/000077500000000000000000000000001445400776100230455ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/.github/workflows/codeql.yml000066400000000000000000000033121445400776100250360ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '41 23 * * 6' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 golang-github-appleboy-gin-jwt-2.9.1/.github/workflows/go.yml000066400000000000000000000031421445400776100241750ustar00rootroot00000000000000name: Run Tests on: push: branches: - master pull_request: branches: - master jobs: lint: runs-on: ubuntu-latest steps: - name: Setup go uses: actions/setup-go@v3 with: go-version: '^1.16' - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3 with: version: v1.50.1 args: --verbose test: strategy: matrix: os: [ubuntu-latest] go: [1.17, 1.18, 1.19] include: - os: ubuntu-latest go-build: ~/.cache/go-build - os: macos-latest go-build: ~/Library/Caches/go-build name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} env: GO111MODULE: on GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Checkout Code uses: actions/checkout@v3 with: ref: ${{ github.ref }} - uses: actions/cache@v3 with: path: | ${{ matrix.go-build }} ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run Tests run: | go test -v -covermode=atomic -coverprofile=coverage.out - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: flags: ${{ matrix.os }},go-${{ matrix.go }} golang-github-appleboy-gin-jwt-2.9.1/.github/workflows/goreleaser.yml000066400000000000000000000011301445400776100257130ustar00rootroot00000000000000name: Goreleaser on: push: tags: - '*' jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v3 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v3 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} golang-github-appleboy-gin-jwt-2.9.1/.gitignore000066400000000000000000000004511445400776100214400ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof .DS_Store .vscode coverage.out golang-github-appleboy-gin-jwt-2.9.1/.goreleaser.yaml000066400000000000000000000031411445400776100225410ustar00rootroot00000000000000project_name: gin-jwt builds: - # If true, skip the build. # Useful for library projects. # Default is false skip: true changelog: # Set it to true if you wish to skip the changelog generation. # This may result in an empty release notes on GitHub/GitLab/Gitea. skip: false # Changelog generation implementation to use. # # Valid options are: # - `git`: uses `git log`; # - `github`: uses the compare GitHub API, appending the author login to the changelog. # - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog. # - `github-native`: uses the GitHub release notes generation API, disables the groups feature. # # Defaults to `git`. use: git # Sorts the changelog by the commit's messages. # Could either be asc, desc or empty # Default is empty sort: asc # Group commits messages by given regex and title. # Order value defines the order of the groups. # Proving no regex means all commits will be grouped under the default group. # Groups are disabled when using github-native, as it already groups things by itself. # # Default is no groups. groups: - title: Features regexp: "^.*feat[(\\w)]*:+.*$" order: 0 - title: 'Bug fixes' regexp: "^.*fix[(\\w)]*:+.*$" order: 1 - title: 'Enhancements' regexp: "^.*chore[(\\w)]*:+.*$" order: 2 - title: Others order: 999 filters: # Commit messages matching the regexp listed here will be removed from # the changelog # Default is empty exclude: - '^docs' - 'CICD' - typo - 'CI' golang-github-appleboy-gin-jwt-2.9.1/LICENSE000066400000000000000000000020631445400776100204560ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Bo-Yi Wu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-appleboy-gin-jwt-2.9.1/README.md000066400000000000000000000315141445400776100207330ustar00rootroot00000000000000# JWT Middleware for Gin Framework [![Run Tests](https://github.com/appleboy/gin-jwt/actions/workflows/go.yml/badge.svg)](https://github.com/appleboy/gin-jwt/actions/workflows/go.yml) [![GitHub tag](https://img.shields.io/github/tag/appleboy/gin-jwt.svg)](https://github.com/appleboy/gin-jwt/releases) [![GoDoc](https://godoc.org/github.com/appleboy/gin-jwt?status.svg)](https://godoc.org/github.com/appleboy/gin-jwt) [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/gin-jwt)](https://goreportcard.com/report/github.com/appleboy/gin-jwt) [![codecov](https://codecov.io/gh/appleboy/gin-jwt/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/gin-jwt) [![codebeat badge](https://codebeat.co/badges/c4015f07-df23-4c7c-95ba-9193a12e14b1)](https://codebeat.co/projects/github-com-appleboy-gin-jwt) [![Sourcegraph](https://sourcegraph.com/github.com/appleboy/gin-jwt/-/badge.svg)](https://sourcegraph.com/github.com/appleboy/gin-jwt?badge) This is a middleware for [Gin](https://github.com/gin-gonic/gin) framework. It uses [jwt-go](https://github.com/golang-jwt/jwt) to provide a jwt authentication middleware. It provides additional handler functions to provide the `login` api that will generate the token and an additional `refresh` handler that can be used to refresh tokens. ## Security Issue Simple HS256 JWT token brute force cracker. Effective only to crack JWT tokens with weak secrets. **Recommendation**: Use strong long secrets or `RS256` tokens. See the [jwt-cracker repository](https://github.com/lmammino/jwt-cracker). ## Usage Download and install using [go module](https://blog.golang.org/using-go-modules): ```sh export GO111MODULE=on go get github.com/appleboy/gin-jwt/v2 ``` Import it in your code: ```go import "github.com/appleboy/gin-jwt/v2" ``` Download and install without using [go module](https://blog.golang.org/using-go-modules): ```sh go get github.com/appleboy/gin-jwt ``` Import it in your code: ```go import "github.com/appleboy/gin-jwt" ``` ## Example Please see [the example file](_example/basic/server.go) and you can use `ExtractClaims` to fetch user data. ```go package main import ( "log" "net/http" "os" "time" jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" ) type login struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } var identityKey = "id" func helloHandler(c *gin.Context) { claims := jwt.ExtractClaims(c) user, _ := c.Get(identityKey) c.JSON(200, gin.H{ "userID": claims[identityKey], "userName": user.(*User).UserName, "text": "Hello World.", }) } // User demo type User struct { UserName string FirstName string LastName string } func main() { port := os.Getenv("PORT") r := gin.Default() if port == "" { port = "8000" } // the jwt middleware authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ Realm: "test zone", Key: []byte("secret key"), Timeout: time.Hour, MaxRefresh: time.Hour, IdentityKey: identityKey, PayloadFunc: func(data interface{}) jwt.MapClaims { if v, ok := data.(*User); ok { return jwt.MapClaims{ identityKey: v.UserName, } } return jwt.MapClaims{} }, IdentityHandler: func(c *gin.Context) interface{} { claims := jwt.ExtractClaims(c) return &User{ UserName: claims[identityKey].(string), } }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals login if err := c.ShouldBind(&loginVals); err != nil { return "", jwt.ErrMissingLoginValues } userID := loginVals.Username password := loginVals.Password if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") { return &User{ UserName: userID, LastName: "Bo-Yi", FirstName: "Wu", }, nil } return nil, jwt.ErrFailedAuthentication }, Authorizator: func(data interface{}, c *gin.Context) bool { if v, ok := data.(*User); ok && v.UserName == "admin" { return true } return false }, Unauthorized: func(c *gin.Context, code int, message string) { c.JSON(code, gin.H{ "code": code, "message": message, }) }, // TokenLookup is a string in the form of ":" that is used // to extract token from the request. // Optional. Default value "header:Authorization". // Possible values: // - "header:" // - "query:" // - "cookie:" // - "param:" TokenLookup: "header: Authorization, query: token, cookie: jwt", // TokenLookup: "query:token", // TokenLookup: "cookie:token", // TokenHeadName is a string in the header. Default value is "Bearer" TokenHeadName: "Bearer", // TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens. TimeFunc: time.Now, }) if err != nil { log.Fatal("JWT Error:" + err.Error()) } // When you use jwt.New(), the function is already automatically called for checking, // which means you don't need to call it again. errInit := authMiddleware.MiddlewareInit() if errInit != nil { log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error()) } r.POST("/login", authMiddleware.LoginHandler) r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) { claims := jwt.ExtractClaims(c) log.Printf("NoRoute claims: %#v\n", claims) c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"}) }) auth := r.Group("/auth") // Refresh time can be longer than token timeout auth.GET("/refresh_token", authMiddleware.RefreshHandler) auth.Use(authMiddleware.MiddlewareFunc()) { auth.GET("/hello", helloHandler) } if err := http.ListenAndServe(":"+port, r); err != nil { log.Fatal(err) } } ``` ## Demo Please run _example/basic/server.go file and listen `8000` port. ```sh go run _example/basic/server.go ``` Download and install [httpie](https://github.com/jkbrzt/httpie) CLI HTTP client. ### Login API ```sh http -v --json POST localhost:8000/login username=admin password=admin ``` Output screenshot ![api screenshot](screenshot/login.png) ### Refresh token API ```bash http -v -f GET localhost:8000/auth/refresh_token "Authorization:Bearer xxxxxxxxx" "Content-Type: application/json" ``` Output screenshot ![api screenshot](screenshot/refresh_token.png) ### Hello world Please login as `admin` and password as `admin` ```bash http -f GET localhost:8000/auth/hello "Authorization:Bearer xxxxxxxxx" "Content-Type: application/json" ``` Response message `200 OK`: ```sh HTTP/1.1 200 OK Content-Length: 24 Content-Type: application/json; charset=utf-8 Date: Sat, 19 Mar 2016 03:02:57 GMT { "text": "Hello World.", "userID": "admin" } ``` ### Authorization Please login as `test` and password as `test` ```bash http -f GET localhost:8000/auth/hello "Authorization:Bearer xxxxxxxxx" "Content-Type: application/json" ``` Response message `403 Forbidden`: ```sh HTTP/1.1 403 Forbidden Content-Length: 62 Content-Type: application/json; charset=utf-8 Date: Sat, 19 Mar 2016 03:05:40 GMT Www-Authenticate: JWT realm=test zone { "code": 403, "message": "You don't have permission to access." } ``` ### Cookie Token Use these options for setting the JWT in a cookie. See the Mozilla [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies) for more information on these options. ```go SendCookie: true, SecureCookie: false, //non HTTPS dev environments CookieHTTPOnly: true, // JS can't modify CookieDomain: "localhost:8080", CookieName: "token", // default jwt TokenLookup: "cookie:token", CookieSameSite: http.SameSiteDefaultMode, //SameSiteDefaultMode, SameSiteLaxMode, SameSiteStrictMode, SameSiteNoneMode ``` ### Login request flow (using the LoginHandler) PROVIDED: `LoginHandler` This is a provided function to be called on any login endpoint, which will trigger the flow described below. REQUIRED: `Authenticator` This function should verify the user credentials given the gin context (i.e. password matches hashed password for a given user email, and any other authentication logic). Then the authenticator should return a struct or map that contains the user data that will be embedded in the jwt token. This might be something like an account id, role, is_verified, etc. After having successfully authenticated, the data returned from the authenticator is passed in as a parameter into the `PayloadFunc`, which is used to embed the user identifiers mentioned above into the jwt token. If an error is returned, the `Unauthorized` function is used (explained below). OPTIONAL: `PayloadFunc` This function is called after having successfully authenticated (logged in). It should take whatever was returned from `Authenticator` and convert it into `MapClaims` (i.e. map[string]interface{}). A typical use case of this function is for when `Authenticator` returns a struct which holds the user identifiers, and that struct needs to be converted into a map. `MapClaims` should include one element that is [`IdentityKey` (default is "identity"): some_user_identity]. The elements of `MapClaims` returned in `PayloadFunc` will be embedded within the jwt token (as token claims). When users pass in their token on subsequent requests, you can get these claims back by using `ExtractClaims`. OPTIONAL: `LoginResponse` After having successfully authenticated with `Authenticator`, created the jwt token using the identifiers from map returned from `PayloadFunc`, and set it as a cookie if `SendCookie` is enabled, this function is called. It is used to handle any post-login logic. This might look something like using the gin context to return a JSON of the token back to the user. ### Subsequent requests on endpoints requiring jwt token (using MiddlewareFunc) PROVIDED: `MiddlewareFunc` This is gin middleware that should be used within any endpoints that require the jwt token to be present. This middleware will parse the request headers for the token if it exists, and check that the jwt token is valid (not expired, correct signature). Then it will call `IdentityHandler` followed by `Authorizator`. If `Authorizator` passes and all of the previous token validity checks passed, the middleware will continue the request. If any of these checks fail, the `Unauthorized` function is used (explained below). OPTIONAL: `IdentityHandler` The default of this function is likely sufficient for your needs. The purpose of this function is to fetch the user identity from claims embedded within the jwt token, and pass this identity value to `Authorizator`. This function assumes [`IdentityKey`: some_user_identity] is one of the attributes embedded within the claims of the jwt token (determined by `PayloadFunc`). OPTIONAL: `Authorizator` Given the user identity value (`data` parameter) and the gin context, this function should check if the user is authorized to be reaching this endpoint (on the endpoints where the `MiddlewareFunc` applies). This function should likely use `ExtractClaims` to check if the user has the sufficient permissions to reach this endpoint, as opposed to hitting the database on every request. This function should return true if the user is authorized to continue through with the request, or false if they are not authorized (where `Unauthorized` will be called). ### Logout Request flow (using LogoutHandler) PROVIDED: `LogoutHandler` This is a provided function to be called on any logout endpoint, which will clear any cookies if `SendCookie` is set, and then call `LogoutResponse`. OPTIONAL: `LogoutResponse` This should likely just return back to the user the http status code, if logout was successful or not. ### Refresh Request flow (using RefreshHandler) PROVIDED: `RefreshHandler`: This is a provided function to be called on any refresh token endpoint. If the token passed in is was issued within the `MaxRefreshTime` time frame, then this handler will create/set a new token similar to the `LoginHandler`, and pass this token into `RefreshResponse` OPTIONAL: `RefreshResponse`: This should likely return a JSON of the token back to the user, similar to `LoginResponse` ### Failures with logging in, bad tokens, or lacking privileges OPTIONAL `Unauthorized`: On any error logging in, authorizing the user, or when there was no token or a invalid token passed in with the request, the following will happen. The gin context will be aborted depending on `DisabledAbort`, then `HTTPStatusMessageFunc` is called which by default converts the error into a string. Finally the `Unauthorized` function will be called. This function should likely return a JSON containing the http error code and error message to the user. golang-github-appleboy-gin-jwt-2.9.1/_example/000077500000000000000000000000001445400776100212425ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/_example/basic/000077500000000000000000000000001445400776100223235ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/_example/basic/server.go000066400000000000000000000072311445400776100241630ustar00rootroot00000000000000package main import ( "log" "net/http" "os" "time" "github.com/gin-gonic/gin" jwt "github.com/appleboy/gin-jwt/v2" ) type login struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } var identityKey = "id" func helloHandler(c *gin.Context) { claims := jwt.ExtractClaims(c) user, _ := c.Get(identityKey) c.JSON(200, gin.H{ "userID": claims[identityKey], "userName": user.(*User).UserName, "text": "Hello World.", }) } // User demo type User struct { UserName string FirstName string LastName string } func main() { port := os.Getenv("PORT") r := gin.Default() if port == "" { port = "8000" } // the jwt middleware authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ Realm: "test zone", Key: []byte("secret key"), Timeout: time.Hour, MaxRefresh: time.Hour, IdentityKey: identityKey, PayloadFunc: func(data interface{}) jwt.MapClaims { if v, ok := data.(*User); ok { return jwt.MapClaims{ identityKey: v.UserName, } } return jwt.MapClaims{} }, IdentityHandler: func(c *gin.Context) interface{} { claims := jwt.ExtractClaims(c) return &User{ UserName: claims[identityKey].(string), } }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals login if err := c.ShouldBind(&loginVals); err != nil { return "", jwt.ErrMissingLoginValues } userID := loginVals.Username password := loginVals.Password if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") { return &User{ UserName: userID, LastName: "Bo-Yi", FirstName: "Wu", }, nil } return nil, jwt.ErrFailedAuthentication }, Authorizator: func(data interface{}, c *gin.Context) bool { if v, ok := data.(*User); ok && v.UserName == "admin" { return true } return false }, Unauthorized: func(c *gin.Context, code int, message string) { c.JSON(code, gin.H{ "code": code, "message": message, }) }, // TokenLookup is a string in the form of ":" that is used // to extract token from the request. // Optional. Default value "header:Authorization". // Possible values: // - "header:" // - "query:" // - "cookie:" // - "param:" TokenLookup: "header: Authorization, query: token, cookie: jwt", // TokenLookup: "query:token", // TokenLookup: "cookie:token", // TokenHeadName is a string in the header. Default value is "Bearer" TokenHeadName: "Bearer", // TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens. TimeFunc: time.Now, }) if err != nil { log.Fatal("JWT Error:" + err.Error()) } // When you use jwt.New(), the function is already automatically called for checking, // which means you don't need to call it again. errInit := authMiddleware.MiddlewareInit() if errInit != nil { log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error()) } r.POST("/login", authMiddleware.LoginHandler) r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) { claims := jwt.ExtractClaims(c) log.Printf("NoRoute claims: %#v\n", claims) c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"}) }) auth := r.Group("/auth") // Refresh time can be longer than token timeout auth.GET("/refresh_token", authMiddleware.RefreshHandler) auth.Use(authMiddleware.MiddlewareFunc()) { auth.GET("/hello", helloHandler) } if err := http.ListenAndServe(":"+port, r); err != nil { log.Fatal(err) } } golang-github-appleboy-gin-jwt-2.9.1/auth_jwt.go000066400000000000000000000542511445400776100216330ustar00rootroot00000000000000package jwt import ( "crypto/rsa" "encoding/json" "errors" "net/http" "os" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" ) // MapClaims type that uses the map[string]interface{} for JSON decoding // This is the default claims type if you don't supply one type MapClaims map[string]interface{} // GinJWTMiddleware provides a Json-Web-Token authentication implementation. On failure, a 401 HTTP response // is returned. On success, the wrapped middleware is called, and the userID is made available as // c.Get("userID").(string). // Users can get a token by posting a json request to LoginHandler. The token then needs to be passed in // the Authentication header. Example: Authorization:Bearer XXX_TOKEN_XXX type GinJWTMiddleware struct { // Realm name to display to the user. Required. Realm string // signing algorithm - possible values are HS256, HS384, HS512, RS256, RS384 or RS512 // Optional, default is HS256. SigningAlgorithm string // Secret key used for signing. Required. Key []byte // Callback to retrieve key used for signing. Setting KeyFunc will bypass // all other key settings KeyFunc func(token *jwt.Token) (interface{}, error) // Duration that a jwt token is valid. Optional, defaults to one hour. Timeout time.Duration // This field allows clients to refresh their token until MaxRefresh has passed. // Note that clients can refresh their token in the last moment of MaxRefresh. // This means that the maximum validity timespan for a token is TokenTime + MaxRefresh. // Optional, defaults to 0 meaning not refreshable. MaxRefresh time.Duration // Callback function that should perform the authentication of the user based on login info. // Must return user data as user identifier, it will be stored in Claim Array. Required. // Check error (e) to determine the appropriate error message. Authenticator func(c *gin.Context) (interface{}, error) // Callback function that should perform the authorization of the authenticated user. Called // only after an authentication success. Must return true on success, false on failure. // Optional, default to success. Authorizator func(data interface{}, c *gin.Context) bool // Callback function that will be called during login. // Using this function it is possible to add additional payload data to the webtoken. // The data is then made available during requests via c.Get("JWT_PAYLOAD"). // Note that the payload is not encrypted. // The attributes mentioned on jwt.io can't be used as keys for the map. // Optional, by default no additional data will be set. PayloadFunc func(data interface{}) MapClaims // User can define own Unauthorized func. Unauthorized func(c *gin.Context, code int, message string) // User can define own LoginResponse func. LoginResponse func(c *gin.Context, code int, message string, time time.Time) // User can define own LogoutResponse func. LogoutResponse func(c *gin.Context, code int) // User can define own RefreshResponse func. RefreshResponse func(c *gin.Context, code int, message string, time time.Time) // Set the identity handler function IdentityHandler func(*gin.Context) interface{} // Set the identity key IdentityKey string // TokenLookup is a string in the form of ":" that is used // to extract token from the request. // Optional. Default value "header:Authorization". // Possible values: // - "header:" // - "query:" // - "cookie:" TokenLookup string // TokenHeadName is a string in the header. Default value is "Bearer" TokenHeadName string // TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens. TimeFunc func() time.Time // HTTP Status messages for when something in the JWT middleware fails. // Check error (e) to determine the appropriate error message. HTTPStatusMessageFunc func(e error, c *gin.Context) string // Private key file for asymmetric algorithms PrivKeyFile string // Private Key bytes for asymmetric algorithms // // Note: PrivKeyFile takes precedence over PrivKeyBytes if both are set PrivKeyBytes []byte // Public key file for asymmetric algorithms PubKeyFile string // Private key passphrase PrivateKeyPassphrase string // Public key bytes for asymmetric algorithms. // // Note: PubKeyFile takes precedence over PubKeyBytes if both are set PubKeyBytes []byte // Private key privKey *rsa.PrivateKey // Public key pubKey *rsa.PublicKey // Optionally return the token as a cookie SendCookie bool // Duration that a cookie is valid. Optional, by default equals to Timeout value. CookieMaxAge time.Duration // Allow insecure cookies for development over http SecureCookie bool // Allow cookies to be accessed client side for development CookieHTTPOnly bool // Allow cookie domain change for development CookieDomain string // SendAuthorization allow return authorization header for every request SendAuthorization bool // Disable abort() of context. DisabledAbort bool // CookieName allow cookie name change for development CookieName string // CookieSameSite allow use http.SameSite cookie param CookieSameSite http.SameSite // ParseOptions allow to modify jwt's parser methods ParseOptions []jwt.ParserOption } var ( // ErrMissingSecretKey indicates Secret key is required ErrMissingSecretKey = errors.New("secret key is required") // ErrForbidden when HTTP status 403 is given ErrForbidden = errors.New("you don't have permission to access this resource") // ErrMissingAuthenticatorFunc indicates Authenticator is required ErrMissingAuthenticatorFunc = errors.New("ginJWTMiddleware.Authenticator func is undefined") // ErrMissingLoginValues indicates a user tried to authenticate without username or password ErrMissingLoginValues = errors.New("missing Username or Password") // ErrFailedAuthentication indicates authentication failed, could be faulty username or password ErrFailedAuthentication = errors.New("incorrect Username or Password") // ErrFailedTokenCreation indicates JWT Token failed to create, reason unknown ErrFailedTokenCreation = errors.New("failed to create JWT Token") // ErrExpiredToken indicates JWT token has expired. Can't refresh. ErrExpiredToken = errors.New("token is expired") // in practice, this is generated from the jwt library not by us // ErrEmptyAuthHeader can be thrown if authing with a HTTP header, the Auth header needs to be set ErrEmptyAuthHeader = errors.New("auth header is empty") // ErrMissingExpField missing exp field in token ErrMissingExpField = errors.New("missing exp field") // ErrWrongFormatOfExp field must be float64 format ErrWrongFormatOfExp = errors.New("exp must be float64 format") // ErrInvalidAuthHeader indicates auth header is invalid, could for example have the wrong Realm name ErrInvalidAuthHeader = errors.New("auth header is invalid") // ErrEmptyQueryToken can be thrown if authing with URL Query, the query token variable is empty ErrEmptyQueryToken = errors.New("query token is empty") // ErrEmptyCookieToken can be thrown if authing with a cookie, the token cookie is empty ErrEmptyCookieToken = errors.New("cookie token is empty") // ErrEmptyParamToken can be thrown if authing with parameter in path, the parameter in path is empty ErrEmptyParamToken = errors.New("parameter token is empty") // ErrInvalidSigningAlgorithm indicates signing algorithm is invalid, needs to be HS256, HS384, HS512, RS256, RS384 or RS512 ErrInvalidSigningAlgorithm = errors.New("invalid signing algorithm") // ErrNoPrivKeyFile indicates that the given private key is unreadable ErrNoPrivKeyFile = errors.New("private key file unreadable") // ErrNoPubKeyFile indicates that the given public key is unreadable ErrNoPubKeyFile = errors.New("public key file unreadable") // ErrInvalidPrivKey indicates that the given private key is invalid ErrInvalidPrivKey = errors.New("private key invalid") // ErrInvalidPubKey indicates the the given public key is invalid ErrInvalidPubKey = errors.New("public key invalid") // IdentityKey default identity key IdentityKey = "identity" ) // New for check error with GinJWTMiddleware func New(m *GinJWTMiddleware) (*GinJWTMiddleware, error) { if err := m.MiddlewareInit(); err != nil { return nil, err } return m, nil } func (mw *GinJWTMiddleware) readKeys() error { err := mw.privateKey() if err != nil { return err } err = mw.publicKey() if err != nil { return err } return nil } func (mw *GinJWTMiddleware) privateKey() error { var keyData []byte if mw.PrivKeyFile == "" { keyData = mw.PrivKeyBytes } else { filecontent, err := os.ReadFile(mw.PrivKeyFile) if err != nil { return ErrNoPrivKeyFile } keyData = filecontent } if mw.PrivateKeyPassphrase != "" { //nolint:staticcheck key, err := jwt.ParseRSAPrivateKeyFromPEMWithPassword(keyData, mw.PrivateKeyPassphrase) if err != nil { return ErrInvalidPrivKey } mw.privKey = key return nil } key, err := jwt.ParseRSAPrivateKeyFromPEM(keyData) if err != nil { return ErrInvalidPrivKey } mw.privKey = key return nil } func (mw *GinJWTMiddleware) publicKey() error { var keyData []byte if mw.PubKeyFile == "" { keyData = mw.PubKeyBytes } else { filecontent, err := os.ReadFile(mw.PubKeyFile) if err != nil { return ErrNoPubKeyFile } keyData = filecontent } key, err := jwt.ParseRSAPublicKeyFromPEM(keyData) if err != nil { return ErrInvalidPubKey } mw.pubKey = key return nil } func (mw *GinJWTMiddleware) usingPublicKeyAlgo() bool { switch mw.SigningAlgorithm { case "RS256", "RS512", "RS384": return true } return false } // MiddlewareInit initialize jwt configs. func (mw *GinJWTMiddleware) MiddlewareInit() error { if mw.TokenLookup == "" { mw.TokenLookup = "header:Authorization" } if mw.SigningAlgorithm == "" { mw.SigningAlgorithm = "HS256" } if mw.Timeout == 0 { mw.Timeout = time.Hour } if mw.TimeFunc == nil { mw.TimeFunc = time.Now } mw.TokenHeadName = strings.TrimSpace(mw.TokenHeadName) if len(mw.TokenHeadName) == 0 { mw.TokenHeadName = "Bearer" } if mw.Authorizator == nil { mw.Authorizator = func(data interface{}, c *gin.Context) bool { return true } } if mw.Unauthorized == nil { mw.Unauthorized = func(c *gin.Context, code int, message string) { c.JSON(code, gin.H{ "code": code, "message": message, }) } } if mw.LoginResponse == nil { mw.LoginResponse = func(c *gin.Context, code int, token string, expire time.Time) { c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "token": token, "expire": expire.Format(time.RFC3339), }) } } if mw.LogoutResponse == nil { mw.LogoutResponse = func(c *gin.Context, code int) { c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, }) } } if mw.RefreshResponse == nil { mw.RefreshResponse = func(c *gin.Context, code int, token string, expire time.Time) { c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "token": token, "expire": expire.Format(time.RFC3339), }) } } if mw.IdentityKey == "" { mw.IdentityKey = IdentityKey } if mw.IdentityHandler == nil { mw.IdentityHandler = func(c *gin.Context) interface{} { claims := ExtractClaims(c) return claims[mw.IdentityKey] } } if mw.HTTPStatusMessageFunc == nil { mw.HTTPStatusMessageFunc = func(e error, c *gin.Context) string { return e.Error() } } if mw.Realm == "" { mw.Realm = "gin jwt" } if mw.CookieMaxAge == 0 { mw.CookieMaxAge = mw.Timeout } if mw.CookieName == "" { mw.CookieName = "jwt" } // bypass other key settings if KeyFunc is set if mw.KeyFunc != nil { return nil } if mw.usingPublicKeyAlgo() { return mw.readKeys() } if mw.Key == nil { return ErrMissingSecretKey } return nil } // MiddlewareFunc makes GinJWTMiddleware implement the Middleware interface. func (mw *GinJWTMiddleware) MiddlewareFunc() gin.HandlerFunc { return func(c *gin.Context) { mw.middlewareImpl(c) } } func (mw *GinJWTMiddleware) middlewareImpl(c *gin.Context) { claims, err := mw.GetClaimsFromJWT(c) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c)) return } switch v := claims["exp"].(type) { case nil: mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, c)) return case float64: if int64(v) < mw.TimeFunc().Unix() { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c)) return } case json.Number: n, err := v.Int64() if err != nil { mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c)) return } if n < mw.TimeFunc().Unix() { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c)) return } default: mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c)) return } c.Set("JWT_PAYLOAD", claims) identity := mw.IdentityHandler(c) if identity != nil { c.Set(mw.IdentityKey, identity) } if !mw.Authorizator(identity, c) { mw.unauthorized(c, http.StatusForbidden, mw.HTTPStatusMessageFunc(ErrForbidden, c)) return } c.Next() } // GetClaimsFromJWT get claims from JWT token func (mw *GinJWTMiddleware) GetClaimsFromJWT(c *gin.Context) (MapClaims, error) { token, err := mw.ParseToken(c) if err != nil { return nil, err } if mw.SendAuthorization { if v, ok := c.Get("JWT_TOKEN"); ok { c.Header("Authorization", mw.TokenHeadName+" "+v.(string)) } } claims := MapClaims{} for key, value := range token.Claims.(jwt.MapClaims) { claims[key] = value } return claims, nil } // LoginHandler can be used by clients to get a jwt token. // Payload needs to be json in the form of {"username": "USERNAME", "password": "PASSWORD"}. // Reply will be of the form {"token": "TOKEN"}. func (mw *GinJWTMiddleware) LoginHandler(c *gin.Context) { if mw.Authenticator == nil { mw.unauthorized(c, http.StatusInternalServerError, mw.HTTPStatusMessageFunc(ErrMissingAuthenticatorFunc, c)) return } data, err := mw.Authenticator(c) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c)) return } // Create the token token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) claims := token.Claims.(jwt.MapClaims) if mw.PayloadFunc != nil { for key, value := range mw.PayloadFunc(data) { claims[key] = value } } expire := mw.TimeFunc().Add(mw.Timeout) claims["exp"] = expire.Unix() claims["orig_iat"] = mw.TimeFunc().Unix() tokenString, err := mw.signedString(token) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrFailedTokenCreation, c)) return } // set cookie if mw.SendCookie { expireCookie := mw.TimeFunc().Add(mw.CookieMaxAge) maxage := int(expireCookie.Unix() - mw.TimeFunc().Unix()) if mw.CookieSameSite != 0 { c.SetSameSite(mw.CookieSameSite) } c.SetCookie( mw.CookieName, tokenString, maxage, "/", mw.CookieDomain, mw.SecureCookie, mw.CookieHTTPOnly, ) } mw.LoginResponse(c, http.StatusOK, tokenString, expire) } // LogoutHandler can be used by clients to remove the jwt cookie (if set) func (mw *GinJWTMiddleware) LogoutHandler(c *gin.Context) { // delete auth cookie if mw.SendCookie { if mw.CookieSameSite != 0 { c.SetSameSite(mw.CookieSameSite) } c.SetCookie( mw.CookieName, "", -1, "/", mw.CookieDomain, mw.SecureCookie, mw.CookieHTTPOnly, ) } mw.LogoutResponse(c, http.StatusOK) } func (mw *GinJWTMiddleware) signedString(token *jwt.Token) (string, error) { var tokenString string var err error if mw.usingPublicKeyAlgo() { tokenString, err = token.SignedString(mw.privKey) } else { tokenString, err = token.SignedString(mw.Key) } return tokenString, err } // RefreshHandler can be used to refresh a token. The token still needs to be valid on refresh. // Shall be put under an endpoint that is using the GinJWTMiddleware. // Reply will be of the form {"token": "TOKEN"}. func (mw *GinJWTMiddleware) RefreshHandler(c *gin.Context) { tokenString, expire, err := mw.RefreshToken(c) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c)) return } mw.RefreshResponse(c, http.StatusOK, tokenString, expire) } // RefreshToken refresh token and check if token is expired func (mw *GinJWTMiddleware) RefreshToken(c *gin.Context) (string, time.Time, error) { claims, err := mw.CheckIfTokenExpire(c) if err != nil { return "", time.Now(), err } // Create the token newToken := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) newClaims := newToken.Claims.(jwt.MapClaims) for key := range claims { newClaims[key] = claims[key] } expire := mw.TimeFunc().Add(mw.Timeout) newClaims["exp"] = expire.Unix() newClaims["orig_iat"] = mw.TimeFunc().Unix() tokenString, err := mw.signedString(newToken) if err != nil { return "", time.Now(), err } // set cookie if mw.SendCookie { expireCookie := mw.TimeFunc().Add(mw.CookieMaxAge) maxage := int(expireCookie.Unix() - time.Now().Unix()) if mw.CookieSameSite != 0 { c.SetSameSite(mw.CookieSameSite) } c.SetCookie( mw.CookieName, tokenString, maxage, "/", mw.CookieDomain, mw.SecureCookie, mw.CookieHTTPOnly, ) } return tokenString, expire, nil } // CheckIfTokenExpire check if token expire func (mw *GinJWTMiddleware) CheckIfTokenExpire(c *gin.Context) (jwt.MapClaims, error) { token, err := mw.ParseToken(c) if err != nil { // If we receive an error, and the error is anything other than a single // ValidationErrorExpired, we want to return the error. // If the error is just ValidationErrorExpired, we want to continue, as we can still // refresh the token if it's within the MaxRefresh time. // (see https://github.com/appleboy/gin-jwt/issues/176) validationErr, ok := err.(*jwt.ValidationError) if !ok || validationErr.Errors != jwt.ValidationErrorExpired { return nil, err } } claims := token.Claims.(jwt.MapClaims) origIat := int64(claims["orig_iat"].(float64)) if origIat < mw.TimeFunc().Add(-mw.MaxRefresh).Unix() { return nil, ErrExpiredToken } return claims, nil } // TokenGenerator method that clients can use to get a jwt token. func (mw *GinJWTMiddleware) TokenGenerator(data interface{}) (string, time.Time, error) { token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) claims := token.Claims.(jwt.MapClaims) if mw.PayloadFunc != nil { for key, value := range mw.PayloadFunc(data) { claims[key] = value } } expire := mw.TimeFunc().Add(mw.Timeout) claims["exp"] = expire.Unix() claims["orig_iat"] = mw.TimeFunc().Unix() tokenString, err := mw.signedString(token) if err != nil { return "", time.Time{}, err } return tokenString, expire, nil } func (mw *GinJWTMiddleware) jwtFromHeader(c *gin.Context, key string) (string, error) { authHeader := c.Request.Header.Get(key) if authHeader == "" { return "", ErrEmptyAuthHeader } parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == mw.TokenHeadName) { return "", ErrInvalidAuthHeader } return parts[1], nil } func (mw *GinJWTMiddleware) jwtFromQuery(c *gin.Context, key string) (string, error) { token := c.Query(key) if token == "" { return "", ErrEmptyQueryToken } return token, nil } func (mw *GinJWTMiddleware) jwtFromCookie(c *gin.Context, key string) (string, error) { cookie, _ := c.Cookie(key) if cookie == "" { return "", ErrEmptyCookieToken } return cookie, nil } func (mw *GinJWTMiddleware) jwtFromParam(c *gin.Context, key string) (string, error) { token := c.Param(key) if token == "" { return "", ErrEmptyParamToken } return token, nil } func (mw *GinJWTMiddleware) jwtFromForm(c *gin.Context, key string) (string, error) { token := c.PostForm(key) if token == "" { return "", ErrEmptyParamToken } return token, nil } // ParseToken parse jwt token from gin context func (mw *GinJWTMiddleware) ParseToken(c *gin.Context) (*jwt.Token, error) { var token string var err error methods := strings.Split(mw.TokenLookup, ",") for _, method := range methods { if len(token) > 0 { break } parts := strings.Split(strings.TrimSpace(method), ":") k := strings.TrimSpace(parts[0]) v := strings.TrimSpace(parts[1]) switch k { case "header": token, err = mw.jwtFromHeader(c, v) case "query": token, err = mw.jwtFromQuery(c, v) case "cookie": token, err = mw.jwtFromCookie(c, v) case "param": token, err = mw.jwtFromParam(c, v) case "form": token, err = mw.jwtFromForm(c, v) } } if err != nil { return nil, err } if mw.KeyFunc != nil { return jwt.Parse(token, mw.KeyFunc, mw.ParseOptions...) } return jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { if jwt.GetSigningMethod(mw.SigningAlgorithm) != t.Method { return nil, ErrInvalidSigningAlgorithm } if mw.usingPublicKeyAlgo() { return mw.pubKey, nil } // save token string if valid c.Set("JWT_TOKEN", token) return mw.Key, nil }, mw.ParseOptions...) } // ParseTokenString parse jwt token string func (mw *GinJWTMiddleware) ParseTokenString(token string) (*jwt.Token, error) { if mw.KeyFunc != nil { return jwt.Parse(token, mw.KeyFunc, mw.ParseOptions...) } return jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { if jwt.GetSigningMethod(mw.SigningAlgorithm) != t.Method { return nil, ErrInvalidSigningAlgorithm } if mw.usingPublicKeyAlgo() { return mw.pubKey, nil } return mw.Key, nil }, mw.ParseOptions...) } func (mw *GinJWTMiddleware) unauthorized(c *gin.Context, code int, message string) { c.Header("WWW-Authenticate", "JWT realm="+mw.Realm) if !mw.DisabledAbort { c.Abort() } mw.Unauthorized(c, code, message) } // ExtractClaims help to extract the JWT claims func ExtractClaims(c *gin.Context) MapClaims { claims, exists := c.Get("JWT_PAYLOAD") if !exists { return make(MapClaims) } return claims.(MapClaims) } // ExtractClaimsFromToken help to extract the JWT claims from token func ExtractClaimsFromToken(token *jwt.Token) MapClaims { if token == nil { return make(MapClaims) } claims := MapClaims{} for key, value := range token.Claims.(jwt.MapClaims) { claims[key] = value } return claims } // GetToken help to get the JWT token string func GetToken(c *gin.Context) string { token, exists := c.Get("JWT_TOKEN") if !exists { return "" } return token.(string) } golang-github-appleboy-gin-jwt-2.9.1/auth_jwt_test.go000066400000000000000000001037461445400776100226760ustar00rootroot00000000000000package jwt import ( "errors" "fmt" "log" "net/http" "os" "reflect" "strings" "testing" "time" "github.com/appleboy/gofight/v2" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" ) // Login form structure. type Login struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } var ( key = []byte("secret key") defaultAuthenticator = func(c *gin.Context) (interface{}, error) { var loginVals Login userID := loginVals.Username password := loginVals.Password if userID == "admin" && password == "admin" { return userID, nil } return userID, ErrFailedAuthentication } ) func makeTokenString(SigningAlgorithm string, username string) string { if SigningAlgorithm == "" { SigningAlgorithm = "HS256" } token := jwt.New(jwt.GetSigningMethod(SigningAlgorithm)) claims := token.Claims.(jwt.MapClaims) claims["identity"] = username claims["exp"] = time.Now().Add(time.Hour).Unix() claims["orig_iat"] = time.Now().Unix() var tokenString string if SigningAlgorithm == "RS256" { keyData, _ := os.ReadFile("testdata/jwtRS256.key") signKey, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData) tokenString, _ = token.SignedString(signKey) } else { tokenString, _ = token.SignedString(key) } return tokenString } func keyFunc(token *jwt.Token) (interface{}, error) { cert, err := os.ReadFile("testdata/jwtRS256.key.pub") if err != nil { return nil, err } return jwt.ParseRSAPublicKeyFromPEM(cert) } func TestMissingKey(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "test zone", Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, }) assert.Error(t, err) assert.Equal(t, ErrMissingSecretKey, err) } func TestMissingPrivKey(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyFile: "nonexisting", }) assert.Error(t, err) assert.Equal(t, ErrNoPrivKeyFile, err) } func TestMissingPubKey(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyFile: "testdata/jwtRS256.key", PubKeyFile: "nonexisting", }) assert.Error(t, err) assert.Equal(t, ErrNoPubKeyFile, err) } func TestInvalidPrivKey(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyFile: "testdata/invalidprivkey.key", PubKeyFile: "testdata/jwtRS256.key.pub", }) assert.Error(t, err) assert.Equal(t, ErrInvalidPrivKey, err) } func TestInvalidPrivKeyBytes(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyBytes: []byte("Invalid_Private_Key"), PubKeyFile: "testdata/jwtRS256.key.pub", }) assert.Error(t, err) assert.Equal(t, ErrInvalidPrivKey, err) } func TestInvalidPubKey(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyFile: "testdata/jwtRS256.key", PubKeyFile: "testdata/invalidpubkey.key", }) assert.Error(t, err) assert.Equal(t, ErrInvalidPubKey, err) } func TestInvalidPubKeyBytes(t *testing.T) { _, err := New(&GinJWTMiddleware{ Realm: "zone", SigningAlgorithm: "RS256", PrivKeyFile: "testdata/jwtRS256.key", PubKeyBytes: []byte("Invalid_Private_Key"), }) assert.Error(t, err) assert.Equal(t, ErrInvalidPubKey, err) } func TestMissingTimeOut(t *testing.T) { authMiddleware, err := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Authenticator: defaultAuthenticator, }) assert.NoError(t, err) assert.Equal(t, time.Hour, authMiddleware.Timeout) } func TestMissingTokenLookup(t *testing.T) { authMiddleware, err := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Authenticator: defaultAuthenticator, }) assert.NoError(t, err) assert.Equal(t, "header:Authorization", authMiddleware.TokenLookup) } func helloHandler(c *gin.Context) { c.JSON(200, gin.H{ "text": "Hello World.", "token": GetToken(c), }) } func ginHandler(auth *GinJWTMiddleware) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() r.POST("/login", auth.LoginHandler) r.POST("/logout", auth.LogoutHandler) // test token in path r.GET("/g/:token/refresh_token", auth.RefreshHandler) group := r.Group("/auth") // Refresh time can be longer than token timeout group.GET("/refresh_token", auth.RefreshHandler) group.Use(auth.MiddlewareFunc()) { group.GET("/hello", helloHandler) } return r } func TestMissingAuthenticatorForLoginHandler(t *testing.T) { authMiddleware, err := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, }) assert.NoError(t, err) handler := ginHandler(authMiddleware) r := gofight.New() r.POST("/login"). SetJSON(gofight.D{ "username": "admin", "password": "admin", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, ErrMissingAuthenticatorFunc.Error(), message.String()) assert.Equal(t, http.StatusInternalServerError, r.Code) }) } func TestLoginHandler(t *testing.T) { // the middleware to test cookieName := "jwt" cookieDomain := "example.com" authMiddleware, err := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, PayloadFunc: func(data interface{}) MapClaims { // Set custom claim, to be checked in Authorizator method return MapClaims{"testkey": "testval", "exp": 0} }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals Login if binderr := c.ShouldBind(&loginVals); binderr != nil { return "", ErrMissingLoginValues } userID := loginVals.Username password := loginVals.Password if userID == "admin" && password == "admin" { return userID, nil } return "", ErrFailedAuthentication }, Authorizator: func(user interface{}, c *gin.Context) bool { return true }, LoginResponse: func(c *gin.Context, code int, token string, t time.Time) { cookie, err := c.Cookie("jwt") if err != nil { log.Println(err) } c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "token": token, "expire": t.Format(time.RFC3339), "message": "login successfully", "cookie": cookie, }) }, SendCookie: true, CookieName: cookieName, CookieDomain: cookieDomain, TimeFunc: func() time.Time { return time.Now().Add(time.Duration(5) * time.Minute) }, }) assert.NoError(t, err) handler := ginHandler(authMiddleware) r := gofight.New() r.POST("/login"). SetJSON(gofight.D{ "username": "admin", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, ErrMissingLoginValues.Error(), message.String()) assert.Equal(t, http.StatusUnauthorized, r.Code) //nolint:staticcheck assert.Equal(t, "application/json; charset=utf-8", r.HeaderMap.Get("Content-Type")) }) r.POST("/login"). SetJSON(gofight.D{ "username": "admin", "password": "test", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, ErrFailedAuthentication.Error(), message.String()) assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.POST("/login"). SetJSON(gofight.D{ "username": "admin", "password": "admin", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, "login successfully", message.String()) assert.Equal(t, http.StatusOK, r.Code) //nolint:staticcheck assert.True(t, strings.HasPrefix(r.HeaderMap.Get("Set-Cookie"), "jwt=")) //nolint:staticcheck assert.True(t, strings.HasSuffix(r.HeaderMap.Get("Set-Cookie"), "; Path=/; Domain=example.com; Max-Age=3600")) }) } func TestParseToken(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Test 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS384", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestParseTokenRS256(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, SigningAlgorithm: "RS256", PrivKeyFile: "testdata/jwtRS256.key", PubKeyFile: "testdata/jwtRS256.key.pub", Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Test 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS384", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("RS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestParseTokenKeyFunc(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", KeyFunc: keyFunc, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, // make sure it skips these settings Key: []byte(""), SigningAlgorithm: "RS256", PrivKeyFile: "", PubKeyFile: "", }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Test 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS384", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("RS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestRefreshHandlerRS256(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, SigningAlgorithm: "RS256", PrivKeyFile: "testdata/jwtRS256.key", PubKeyFile: "testdata/jwtRS256.key.pub", SendCookie: true, CookieName: "jwt", Authenticator: defaultAuthenticator, RefreshResponse: func(c *gin.Context, code int, token string, t time.Time) { cookie, err := c.Cookie("jwt") if err != nil { log.Println(err) } c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "token": token, "expire": t.Format(time.RFC3339), "message": "refresh successfully", "cookie": cookie, }) }, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Test 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("RS256", "admin"), }). SetCookie(gofight.H{ "jwt": makeTokenString("RS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") cookie := gjson.Get(r.Body.String(), "cookie") assert.Equal(t, "refresh successfully", message.String()) assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, makeTokenString("RS256", "admin"), cookie.String()) }) } func TestRefreshHandler(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Test 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestExpiredTokenWithinMaxRefreshOnRefreshHandler(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: 2 * time.Hour, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() token := jwt.New(jwt.GetSigningMethod("HS256")) claims := token.Claims.(jwt.MapClaims) claims["identity"] = "admin" claims["exp"] = time.Now().Add(-time.Minute).Unix() claims["orig_iat"] = time.Now().Add(-time.Hour).Unix() tokenString, _ := token.SignedString(key) // We should be able to refresh a token that has expired but is within the MaxRefresh time r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + tokenString, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestExpiredTokenOnRefreshHandler(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() token := jwt.New(jwt.GetSigningMethod("HS256")) claims := token.Claims.(jwt.MapClaims) claims["identity"] = "admin" claims["exp"] = time.Now().Add(time.Hour).Unix() claims["orig_iat"] = 0 tokenString, _ := token.SignedString(key) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + tokenString, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestAuthorizator(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, Authorizator: func(data interface{}, c *gin.Context) bool { return data.(string) == "admin" }, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "test"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusForbidden, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestParseTokenWithJsonNumber(t *testing.T) { authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, ParseOptions: []jwt.ParserOption{jwt.WithJSONNumber()}, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestClaimsDuringAuthorization(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, PayloadFunc: func(data interface{}) MapClaims { if v, ok := data.(MapClaims); ok { return v } if reflect.TypeOf(data).String() != "string" { return MapClaims{} } var testkey string switch data.(string) { case "admin": testkey = "1234" case "test": testkey = "5678" case "Guest": testkey = "" } // Set custom claim, to be checked in Authorizator method return MapClaims{"identity": data.(string), "testkey": testkey, "exp": 0} }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals Login if err := c.BindJSON(&loginVals); err != nil { return "", ErrMissingLoginValues } userID := loginVals.Username password := loginVals.Password if userID == "admin" && password == "admin" { return userID, nil } if userID == "test" && password == "test" { return userID, nil } return "Guest", ErrFailedAuthentication }, Authorizator: func(user interface{}, c *gin.Context) bool { jwtClaims := ExtractClaims(c) if jwtClaims["identity"] == "administrator" { return true } if jwtClaims["testkey"] == "1234" && jwtClaims["identity"] == "admin" { return true } if jwtClaims["testkey"] == "5678" && jwtClaims["identity"] == "test" { return true } return false }, }) r := gofight.New() handler := ginHandler(authMiddleware) userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "administrator", }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) r.POST("/login"). SetJSON(gofight.D{ "username": "admin", "password": "admin", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { token := gjson.Get(r.Body.String(), "token") userToken = token.String() assert.Equal(t, http.StatusOK, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) r.POST("/login"). SetJSON(gofight.D{ "username": "test", "password": "test", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { token := gjson.Get(r.Body.String(), "token") userToken = token.String() assert.Equal(t, http.StatusOK, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func ConvertClaims(claims MapClaims) map[string]interface{} { return map[string]interface{}{} } func TestEmptyClaims(t *testing.T) { var jwtClaims MapClaims // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals Login userID := loginVals.Username password := loginVals.Password if userID == "admin" && password == "admin" { return "", nil } if userID == "test" && password == "test" { return "Administrator", nil } return userID, ErrFailedAuthentication }, Unauthorized: func(c *gin.Context, code int, message string) { assert.Empty(t, ExtractClaims(c)) assert.Empty(t, ConvertClaims(ExtractClaims(c))) c.String(code, message) }, }) r := gofight.New() handler := ginHandler(authMiddleware) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) assert.Empty(t, jwtClaims) } func TestUnauthorized(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer 1234", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestTokenExpire(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: -time.Second, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, }) handler := ginHandler(authMiddleware) r := gofight.New() userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "admin", }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestTokenFromQueryString(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, TokenLookup: "query:token", }) handler := ginHandler(authMiddleware) r := gofight.New() userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "admin", }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/refresh_token?token="+userToken). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestTokenFromParamPath(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, TokenLookup: "param:token", }) handler := ginHandler(authMiddleware) r := gofight.New() userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "admin", }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/g/"+userToken+"/refresh_token"). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestTokenFromCookieString(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, TokenLookup: "cookie:token", }) handler := ginHandler(authMiddleware) r := gofight.New() userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "admin", }) r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { token := gjson.Get(r.Body.String(), "token") assert.Equal(t, http.StatusUnauthorized, r.Code) assert.Equal(t, "", token.String()) }) r.GET("/auth/refresh_token"). SetCookie(gofight.H{ "token": userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) r.GET("/auth/hello"). SetCookie(gofight.H{ "token": userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { token := gjson.Get(r.Body.String(), "token") assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, userToken, token.String()) }) } func TestDefineTokenHeadName(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, TokenHeadName: "JWTTOKEN ", Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "JWTTOKEN " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) } func TestHTTPStatusMessageFunc(t *testing.T) { successError := errors.New("Successful test error") failedError := errors.New("Failed test error") successMessage := "Overwrite error message." authMiddleware, _ := New(&GinJWTMiddleware{ Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, HTTPStatusMessageFunc: func(e error, c *gin.Context) string { if e == successError { return successMessage } return e.Error() }, }) successString := authMiddleware.HTTPStatusMessageFunc(successError, nil) failedString := authMiddleware.HTTPStatusMessageFunc(failedError, nil) assert.Equal(t, successMessage, successString) assert.NotEqual(t, successMessage, failedString) } func TestSendAuthorizationBool(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, SendAuthorization: true, Authorizator: func(data interface{}, c *gin.Context) bool { return data.(string) == "admin" }, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "test"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusForbidden, r.Code) }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { //nolint:staticcheck token := r.HeaderMap.Get("Authorization") assert.Equal(t, "Bearer "+makeTokenString("HS256", "admin"), token) assert.Equal(t, http.StatusOK, r.Code) }) } func TestExpiredTokenOnAuth(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, MaxRefresh: time.Hour * 24, Authenticator: defaultAuthenticator, SendAuthorization: true, Authorizator: func(data interface{}, c *gin.Context) bool { return data.(string) == "admin" }, TimeFunc: func() time.Time { return time.Now().AddDate(0, 0, 1) }, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + makeTokenString("HS256", "admin"), }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestBadTokenOnRefreshHandler(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() r.GET("/auth/refresh_token"). SetHeader(gofight.H{ "Authorization": "Bearer " + "BadToken", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestExpiredField(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, }) handler := ginHandler(authMiddleware) r := gofight.New() token := jwt.New(jwt.GetSigningMethod("HS256")) claims := token.Claims.(jwt.MapClaims) claims["identity"] = "admin" claims["orig_iat"] = 0 tokenString, _ := token.SignedString(key) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + tokenString, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, ErrMissingExpField.Error(), message.String()) assert.Equal(t, http.StatusBadRequest, r.Code) }) // wrong format claims["exp"] = "wrongFormatForExpiryIgnoredByJwtLibrary" tokenString, _ = token.SignedString(key) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + tokenString, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") assert.Equal(t, ErrExpiredToken.Error(), strings.ToLower(message.String())) assert.Equal(t, http.StatusUnauthorized, r.Code) }) } func TestCheckTokenString(t *testing.T) { // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: 1 * time.Second, Authenticator: defaultAuthenticator, Unauthorized: func(c *gin.Context, code int, message string) { c.String(code, message) }, PayloadFunc: func(data interface{}) MapClaims { if v, ok := data.(MapClaims); ok { return v } return nil }, }) handler := ginHandler(authMiddleware) r := gofight.New() userToken, _, _ := authMiddleware.TokenGenerator(MapClaims{ "identity": "admin", }) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) }) token, err := authMiddleware.ParseTokenString(userToken) assert.NoError(t, err) claims := ExtractClaimsFromToken(token) assert.Equal(t, "admin", claims["identity"]) time.Sleep(2 * time.Second) r.GET("/auth/hello"). SetHeader(gofight.H{ "Authorization": "Bearer " + userToken, }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusUnauthorized, r.Code) }) _, err = authMiddleware.ParseTokenString(userToken) assert.Error(t, err) assert.Equal(t, MapClaims{}, ExtractClaimsFromToken(nil)) } func TestLogout(t *testing.T) { cookieName := "jwt" cookieDomain := "example.com" // the middleware to test authMiddleware, _ := New(&GinJWTMiddleware{ Realm: "test zone", Key: key, Timeout: time.Hour, Authenticator: defaultAuthenticator, SendCookie: true, CookieName: cookieName, CookieDomain: cookieDomain, }) handler := ginHandler(authMiddleware) r := gofight.New() r.POST("/logout"). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) //nolint:staticcheck assert.Equal(t, fmt.Sprintf("%s=; Path=/; Domain=%s; Max-Age=0", cookieName, cookieDomain), r.HeaderMap.Get("Set-Cookie")) }) } golang-github-appleboy-gin-jwt-2.9.1/go.mod000066400000000000000000000012131445400776100205530ustar00rootroot00000000000000module github.com/appleboy/gin-jwt/v2 go 1.16 require ( github.com/appleboy/gofight/v2 v2.1.2 github.com/gin-gonic/gin v1.8.1 github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 github.com/mattn/go-isatty v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/stretchr/testify v1.8.1 github.com/tidwall/gjson v1.14.3 golang.org/x/crypto v0.4.0 // indirect golang.org/x/net v0.4.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) golang-github-appleboy-gin-jwt-2.9.1/go.sum000066400000000000000000000302041445400776100206020ustar00rootroot00000000000000github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-appleboy-gin-jwt-2.9.1/screenshot/000077500000000000000000000000001445400776100216255ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/screenshot/login.png000066400000000000000000002655251445400776100234620ustar00rootroot00000000000000PNG  IHDRa|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 751 518 ߄@IDATx} `SI4m -M)Ў*"BѢ(!{Et~l^n_}lM8(8A"(--iwӤIνIz&M"(ͽ>;>܈W`z nOݩbqE)4v`lʓ{fLk:1 p|%f\kǁ.!ሁ3~} tБez46aJlG e~zi nБm8xC4C&&Wh7ga;Wn:$6͇8,tD!Fiۋo/3kpl$ٿ7;2yaM/^ۇZvqBX|*>).vj;J e3C 1%Ɏnjn,5GX~S~N-thv"# rPzoVV OChq&\۷ ,c)j8T$]94R4ցGdKr,ܲkaۋ<\B=bFaഠ w0e/h#/9Lv] / EsOc2r{1jXKŤ6L =Qv8_߭"l1Dn!{l:2WQ@&jÌԯ?lh{-9@:av}{+8Ԩn8m12qk-TJe1e^(E]Qypg%hsktX[ѣ"ж#–"ڳɵݸW-$ ޓ)O﷩ˏCNVb胁=ZqdWe9 [,מxEov6 wA(V*BLŇY? su$'6}a)!o ccMvڀYEL_i>YlDԦ7@epkTKNFZVfC[n^u)'-;UiTزwTeO>8S^`˪lQd{M~eW >]O?JK1,o˔W>t=TeosTnCm܉Nl&`}~quNl[&; 4ЊpblSXUl wf-[Ysy-E;\^j7Qp ,K⹭Gl X ZcUxڣ(n;p<ߒMI1lLa6Rq;$YVlD,\F򓞩cəDN$%08rb1"7Q-<,m>3%mNXi AS&Z;?X?z?͛kT5^ajkhHwCplinQ{"JX ]ˮYnɴf}vۺkaz<5Ul}b'.Ư]9]?E:Yoī={Yy /;MEl.}+fϚ r_.{ Fu`Z8l*4^s,O ϭXY+Gf~ԯ7{!26|=+ GE/W{Z1z~ Y4|ͱx/iϋ\=[uK_~\+ـ 8_͘j0`bcG.Evu:spx|4M{;Qg᩵EWo^H`$euuxR@*mP2&#[PV&Dޥ rLjr=QRC,ѱvTNDSL,d~D kjW!ΐ}juϓГm4 s{5`] cGZ$'& A[:XvCvpBS_] #hMB-J}%zULO5=MH5"Ewaau임݁6㴱Xj#EzhKXO"W9'ߐ⋲:cےE-Q}>gD515mvhWE5ͿS,##n(fWᖓ'. r畐Tr(C.gvUt JꉭB hB_$A7%Ϣ{cWBnfc-0hѢr7h$SڢxLxdтv>w2˄DXPDEQ ʄla򢄽;q_g/lJKbEƮB2qkx> 7J,.Ŭi:t֊o;d왵_[Y_)+ @ށno%70Nɷ%+^I7͇h<0Yٔ dg Ќ,=,(9X+"^TU3^rО*+uDiO`Ҏ KM/ePh+q%\F6`g-/v7#aۈ詀q j\Ϲ`xv34q?*!o1Ľ D]caA ݴo34+v#ځ;P~;\ҦB ak;uT ޙVH'X`w]rcH[f!ɝ^Zy3}hD8.U$ p*Z(7y&Z!wKo׊Bl<\UuA9'?({W4y,4؇ b/ c d:zZA-kzO..wЯQ?2 v&M|kK&Ih,s?- <}BV񍅿j?plnH9vfE${_BиkZ\;;{@]p\V=}#yx_:bqs_um3pTwyz qiF ؋v!k;M~ًz|vGEAP` .0PpS'_M N˵g9$ax<.劉f/srl(H%eԕȻ87lĊ%"IK"W2`0x 3^!YGeuD2'_mEiJ g`E I'@V\9rML30iVtV)ÞϷch=L9Bڪw5`ڥH ן-?xi\MTGs)yE 6bvހ% f CEδx*?ŤKC-g&cp~F>}3M亣/;IvW ?*6?.WCzmψSpX,~;-B}eXEX1.jf-fmzԄ0Wp_ү{jŦ{IDn&rzfyԿU`r KAcx!tvŋ;uޫdq4MFܙ6S^r}s!ޗo % Wmrh=Lz\>)fJ^f{-d ,CzVߍ]!,#}՝&_45&ND[eiZ2| 0s:=l_nW aPZs'υ 0f"~kX'C;&oQ9rmvO-ZmB'bĜ9%AI$ M/Z!*ۊ Q%xVOCʕ To矂pMAr1U3ތ/׊cF)qKYNӌT DS 2#/jmFS~nI~d/ZZŎxMan 5Oܶb8[H"CY-<"eĔo:sK>_ bq> iPcjܲL ډz˅ЦMƊ{Vr xŭNc 4^?fWsݳH}׸dnx`/Rlۋ k}˲nsAδNZZR{zb|û3~Gx $E$rTg,8i#:MWOP'mϞܮ&_R˪Uk7QU4_+4բ°VR;Ᵹ\98$8wp7/>Z1cR g4a-p2EAD{{nXW܍5۪Rs FWk[ ]Tb-N*shׂ=3I;ctagG}ǥ,d@ τ,֡"unoƹֈ5[ho%\ЦO\ SO W?~!܅Xj=+p^7ļ"E6ǝ9VT7B?+&^w͠|1ﶫp˱Tוy_mc5DhjLt7B[\Lnp׻a۸B9[ǞeU35?\i3ϿZ;btܗ~+"SuT bi?6'~z<3v*8 [0&*[_|lG=hYDŽ ''a*`= -[>J/UǝKX)v|P?䤣װ,i3fɕBrv>[eiG\d]ǘ aQ%ߺ^ 9 UmǛ(]P|/7{Kgᘻt92n*ǩ&U*\S+x> 'aO\Goe2 `)+6`A(60CvX'a6c~|A.4]:)jxBxtbô{Ŵzvm ׽)-[ʟWG2܄h}T+4/ʧrՏ-le{?'?,!<[ndadK+y+[]5ieC>X>haǮe^ų::|: dQbW#_Sqj}TSO9!CI<8fiAo0]ykV^lG8PD])NX"Wk/YI9F ׮a{xe߇Gp zj`o^FLz_?*#Oʹ+s d!Kp=ؕ`k@aN[JA}7v y/퇹Qd;~x@fĹ>No7L6 [[:{#r,rXFN~ N[m ux21{z*|| Tc"@FZSn&O+´lz3vF ~h&S0&qg;nZվoځ vpêߣo܅D3HA?EܺhUb„;w.fqgMm+jc}k!#垫0D>¶wbpۊVԫ+[@PI;DmŋtEuB@-{'&/IHurs m_zȵ?W2:Lklhk`1PB;l3eB碸"m{ؕ]s'3U8\Uz/RG ~g–J]@m {W6 ]bJ]`~\Xڋo+G=rn/kdsV]Ul~k ϢY4Mx(=uC:;{_ v.m*ϻ|sj.˭yg/B C]3kW쎦GEɕ@}@%{_ǁj1&` >wӏ=ƋG27Ys,@Pȉ &xۇ, ׎>_X V*nD^Īm0Wіu33ez=0;z_1̥͢ Omzg Cl^Ñ6?H5"UKTO>&m Att$N߈};H>$#k'7& ( cMy30&v}CVcMX`R[މ&@᎖|?y[ Kg\lZcckC`vmwea[6{_&= &T^J0_hLwq?̞Ȟ] \PltaP }F=dψGhP:Cٸȵ?rw/+0+WݳDNyO?uuؼYRmpt؄62b3f!(9р<ɿv3;I p8f(1>d'NtĠ`<,( *y?F~$;g.8<,n j\m wᨍvG@g[>yh!RIa_ˉ~"`fN"ICQ,'RHKKWz(HLs'ZЇiW.(cӠuXҌSW[qs8h?%}]6+ڨ8U->}yysWd<#pn#yĹ~ie͜s8G#p8v9tp8G#\p~A43W#p8G|@@ւA`:Dǃvs #gEEIZ-G#p8@۬`D脒}t 4҇]:sЬa۸ɯ.F(Ǚ(Z.m:aڽ q o܏W҄u$4~߲rUP{xp'х ;ja^i*eSi/&MB얧 {W.7n e$b{<E}">mf6bW_v^{I7K MƩ{iTtT #p87pXLa^@R}h]'Q$qmhEh #grHNf$ǿ h5E2EF 'ѻ~}ΒR'PDFa0JJxST1)H)x ?T.? (0vZ'=v4)I'="Bvdە_}%t#~8'mU` ;?HG#p8@ y'2M5>htGD:\Fr":U$1uŰ7Ɵ܋&)R\ -ePw YF]e1߂yPqWTŢ Lg߅v.܅WT2mLrOF%}7\Q l.p8G ɻevDT7#)X^& +3o@(^E/DYhyF%hʚ$Mdj:lqD);;; Fq%JZ u.:_qR06KUpĔ%tq?~A'=rm v^iH%_ b4~rʥ8G#C yɤmu_tṯ43}*~ Dޛ߃-AZ`~(c˜>;u/'+7. ŗ$a;l+ ,Nie@:ټ{4L=%CCjtLf^ 7EHqA!U| W-ZGG(3  ݁5_ "W!WN])[1O>ŷ;C> w~!W'p8G#A'.q4Oؘ.\V#"tʎ"ѣf>X =) gȫD:qKKlXb8FܝhAc#,XL킘s.n2Bl$5AbhhTшCLLcTL8] b%3nX(?S3g]+iq>w:UoÝ$G#p8yWOnB˼E~5 / Ew?ل3JX@96w,阒Gi-8+^)#eqFE`9x卨d%FM[xfl|9.>ͦvKj_0K f+B GϏ:ٕWh7 G#p8s ]P(b)wb.R`e Z QKy s$dKK;e !Ғ %ࢻg"!.&01w\"L06?v`)8Qm=>.jZ*NB}E]H> &#ĈAbyC7]I GIG#pirF֎I{ڽ1Lubz\=(E"*N]2T 3CgcG9>5@X06s4TWFnB 4G Xc'ElXB{l W<TǽxΏr8G#8BۡePK DȚ8Y#W&Kp@A 1*]6mitQqK?45CE>itHפ:[k0fMG-|UXg!^_Ev%vZ]xG#pA_+8ly%e6$[mw_nk? #ڛE GGotEq[nԶ3,}&M 8F yu GT1RHMYzYaq!zvِM h/]XPXOI#8G# g}]V$nD1'~=o""Z=+}iztZJw|";+@KTE#=oHbZCJr[1v sO@OJ8ti%\%*əBY~Hv);ZDwC ;R\~Gɲ;vtDEҡ{c=g&%&Ԏ`M1~b;PMɏ8G# =;Hۋ[`\2F{]"vOĔ{(h|t{AHܾ+uUs_R5D"UAo `WvvA|Ѕmg#Ϙٸ3~aldCEx[0+Us`@QH:l}p|oÛP5Jx {>yG#p8p t=bdH|C8H XK]iP(L%6< V](rx ^ ks$~[g://|_ 8 tCzP-q޼tZ[;t7"-q_/t0=Z12J\(#YS!V?;8e4uk%סW^HKtm_Q :ۃs±ϡMWR(_D6[0Mw]n3omF6 Kկx9tzG#Cޣ;"QЯfn(/* (em?BV0v4ЭF>˥MŒnmmmDSw+^Z261DfM²1qRv* SbkۉM'=uPֻyB(LD2"x '@1-H8mC%)H`iѝw lOȧbEFcA1zk|ȩ֡V,q"T o)f2m3!$Xt`1"] }qlf;,(st ="˭mCK4$n8N'e_#}cd> 񄽶]0-vtМ]2*p 7L#C+VK" Ga Tka!q8o<@ة*F4Ӯ@^0zH*9G#8Lޣr.r ^c# yϽjHܭu?P$ iy{yr(%F~2!̢EG|D`_lK>;RVւܫafK` VWifbmDye#@.%̫S[c J1w=nR|E讁iV yl1IDiv{gӗ\" rn%V!_"}*TeRҾxH-zDEwfԽQyV8SshXoO*zrdoBNحQUfB<(Θ{H2K{/"zsЙDwa*[]~v1{{FqÛ^X Y P[~p8y@@n TzDM- GBD.f7J;ECJXdŏ{}lMӧ 3wώsЦW\,lO9yg^l)dW.lQpka RL*a &dG )nK @ӆf~5bi"D te/x'xWL}#AiDtcN4:M¶ښ=βQ1 MIMW{^oQZX"9'&oyG>BDO[IIo\0B=0vͶ}pAkc9G#8W8HF^e,kEI7Q,9r},@IDAT4'HJ F%c_cqE ػ$Ī҅vIP:7Sy^5by6,DWry>! $l'PeI=D׵ OvOY[VxZDQ7xCŸ޷ !{j׈tDr%C!Cu3N姣8lv~MH_* זp8GlG yEA7KFz1l(t)ΉkA, b5Yub K%;{4w7ƟmF k;R썆4(|7 [[jEw\9iރ=Mt v`ﯜ@HD32ilRJIN׸.#?"7z|%Gt_Ihu 梧^[~p8kJ[QJI.wPz)hRMq*ę.4rj40>0Ii`d|=#BK;)YYӉ*wkR`"f5-fDIǰ\̮}V vL;]3i++?,zȕ$M~#nsPN)l)]8T}E,9*_"O W;2/EćY\RjW2ṆWLǤ:8\Z g5H)G =z8G#8fG]fwcNr:  䎌B%s-)BB\CaдR˓Ŏ/,O\DTem܇g^驍&b̟.eu<|.mxj t~w_rb"6ѡ{Nzǐl-[3=ݎ_zUO$>pl#H9TM li7nES#T}AClؑ^S߅6 @L9sF3׌B-Myr Wd(֕Yhgv b)n S~,rq\<tVo7\;t"ڊ(GkUhw.Z B^}iה\YҖG>Gі+穗 t֌6c7(h1yײ`Ohox vŅvj1|'Ӝ$vz9~>4sjGpr2[[^AGU{puy_uZ,+?]/k;[kIOB{4IhE;U}W݃.r8G#p!#I nQ4"p >䢐92Hb 4K:'aPwC/iS<2UURPD6t"Me' gL.3\9iCpܓ1 Xʙ6?q _`S céfі'#6 wҾ)oh_?2-74(} l[QYprؾCG#p̼\.s-]yr]C3J 0H蛴~D>$.ƕD창 ]>~rR8,bP_2#+W'YN#@՟L}{+TA1ࣈҐg} S}<fQ(Oc0qP~i[#48-y8-p8G%-_j>?6DٮyS#}$amEW#獆\G#pN~vkp8G#8?{~hʵp8G#p~7 >G#p8~O4ڂ>gEEIƒC>l:Dn }HOg@[hA.O{'WaCZMP.9,O!>3Gp8G#V 7EW->ٸ'xt;DC | eg#}}v9oV40N4}Z?˭JeG"g7:-H/,Iy%Ƣ"ccE ~%6+~p8@Js .F>Dvb >JO|l,)̂ }gp{Gdh P+w%߻j(A[K'Ї:mꆦs/{A4DVhUnóD[RP)s{dRp9$G#B%(ƈ; deyOϡj}"R9Z;ݔ N#FZ:A ߵᯨNEmZygǐ=*>Tu(1]Ph@X6ƓI=tO})XQ,#,'R,DSsAG#\O > Q^I4填ZsÍߜ u˾*޸ (5к{ܔ!8G#~{f~&ͷE,_IIų1dnϰaԑD\TZq6OrCyT>~2(=*̛3ûnט(DP#Z#RC/SVoQW~7^E 0(EsyZ5ٰ݇%w{*FKv*up͢b U}>>vwjwZڤS:;H4ÒnƖ^G@Bu,g5k!fCdc"孀n2ZV8 u- {6"ل_RN#qOMoi쀓ݦ^(GW_^ H: +C*s]@|u(Aߑ3jZx%B ?^vDջ=zX^t\2mÓ@IXoA«;(g׎x_:Qb\vbX[,=Xyp8@?= #j0b=΀]R f/-܀nE4qH@(S.m*,/F4uGOx쬥,ccAd$,'YF)XD$5zC`47N4z2íwׅ =Q!7۩F.1q"bAVqJ*~$KQG99\wT㌘` b+Uo.nL QUh E=N:!6DZEY(+-[ w2 k q)Gs$C6v4RQ_m"w6-񮂜MKk#:w lOZlhh,(FO{ *u2R]ܨyH$렵i`4fɽNM@߇OLT<woŪAO\5hx͂ -pCw/DZ,(s.,}U,=>.{lbP].i1E xlw⟠pd_l/o,8yXJ,uޙ焚_Bj@A-p1˄Z&DW,!?8-lD(ѼN3<6tK%R=;T!_"$mwBU8-I(l`I"SlGf8}ɕH =\ˀ"dX$I%Gyo*&2swiD( :FUَ#TozT3w=nR|E="*X gj ۳-UEBrIG#$qkxFC7DFBD.fM4o(! 63:ɂLQp1utz޷22.HKIXhכyp10k(bK6EwDB\ix/&`0ށ]ĝ zVٙWh^RA\3`򶦳rw_b/}rC8Ɉ^?">D3rrIFu(Z=vr{=6Ul;ԯȾ"u^.+.Ay?G| +|SĝiCwsDш9:FW=.p8-ϔ*Nn08z=bVԟtuȒ##'+U|2 %𱋸"9u!uNMqq4ڿ@QT{ذ=C($p)E/K06@+$)a#ȑ=h u 67.>#a VDrԢ]7PKݳ/'W;4F)M?X6줙kizYM'K.V#.uޟ^)Tõ-O~Ԍ;P뎔k$$+RG\VR/!9Y矚FiE½%#kNJ}J*י&E@{w햇pBpiC]ݛf G 7{L $4Mlܴ` A%a Q%yrhcB g!՝ s8G`'~}2%,,coJ8M r33N4{. F-rj400Ii`d| ]0wǵSUX\YC=XKZމ`<\ZZ4/ϤMLƪ;1MY^ X>*7~$=Lw VA?`bΫoG-u |.B>kDA_ ~ޮ3PYyB&}2uZVZ_#G"xl}E4 2ţ]caEÙ M}":žcXg}.+y3GZG#eF.3{+gIÒ%6&8 y0q;yidLtfi;mso:&}MҔ\ HH!1bllec[-K]IdٖlY;t(o:{CYQ@:D5TsPRBV'n|h=ZlhIkgZz<0e kxL8:y/Dעx 6 磌T}aÊ DZw@ %=2чy)2.$>3Ϗzl(18;D4ȥbtrEh@o9J ڿ jʨ50 e ,5ܟz-*hJV}#t`IrV\(Uq&yՎHnHڢkf]0튜`F]{74R< ԱGJ| ߱5vy_|* 5#I6I'`)SW~ )C&tb-E V'pA)ܞE39IL 0&J1ޏ#fn .fNL<Z?1gp!ږ-Ɩ؍ۀlzm|#!Tw}Q>C.sñfs^bқ˴`L \ϟoOkRzI!pv]c/AG._ww쎰R<J1rИ4+ k~t*?;o=- Кj.,xĭ}6)35-*lF 6bhh N> (";1R54lI͵~at+:z5ma٧!#| !1#"yI{s9`N|fId~⋧w֐Y)j/|AxQsW{8G)6c8.gL 0&p D=Ǿ :nr&7Dg0>gS̎DG2GUc巟q܇D"5%KJkCNы} 'テ}#M/c{tp% )o;Sz#deS/aN}ם|P%uB(FLxh8wBqb 6L 0&" 6.JΕ>BY7$xz-©P;xJNaO&ta%y/ &aĿUPqM_^9\v;ʖL 0&F a3nj CjO8{=" hEl>F)}尫gOF ? }&xdOSQ@.ɓ;s%{+0} wVގP]fzʙ`L \ Q~->2&`L 084mqmL 0&`L \/eL 0&`ItiQ^^B%bߴy^u0yeT-a>l`L 0'0i*,_\LR1v?b8xϯp>),T`U_Jx;8I'(x9?:"uhJzyMvh(a,LƜ&fL 0&PW?"Jz[ N F:5PYwAuCB5럝;5#&}Ќ4;ەtbޚ)DG,i>(.yBuW$| 0&`G`|n%{@6oj^iQap*z+Er|CoT|׳,xޓ|{)j^. 382妘`L 0&01q{m%N:OpU >q$?CF\ >C/yxX`L 0Y%0x/AN|dd:\ ]`ՁonǑ5j7!DڕMw7KEN2ZO,MK[۰IMZ,^E,u‰wƾF鼖FR+aw`w=P [{"'>Ko+r:ȗ,]ox)|SvF:"I '1rj+w%!~'_9:Щlpid`H $>iBMyp}sp/_Cϐr(|Q ʿA&q ڑu6,=ZX2Ӱ~lāЋWXߡ(ZхaE 3d1t WXm2/cO, ;P )5^FnғXF ˖Q'QRbBcضG' Ź !"~(^/WE;e1mÓEA] nåwssis{֩]^6dl_]z8HP_tg0WB7DŽ )fTԙ& \ :o}zGXym!u>?+KX `L 0znBzwXފi]pwضW(FїW?L ]Pvq?PC8Է#@4* w½v6JW=e((ǁr~w}_h-fy-hjrPˡ'tlMnɵ.B&m(*mA2\jl |:q2SA1~ ͷJMd2ҶdQxYO)YfAIMqAL< $ yȫ})]b[>Am7ȓ@&We8?|EpV~s~~t(O씒"/Ǽ5pOBx)S8w`L 0&0} OYR\q2$XjsQFЏ~4} {3.2'3*T=XjI8 Y'rȧB{mҕhwi?Z;h\ uBΉLnK~!K_އ OQ,&OF f( |#^'k* ;n;ٚ>=B}ծH%.<2!\ⓂG )T"'w1D|m,}/JNXq5+џgB@*9pݶ@j"㭀7>^~ W`L \bkPא=a8^솼--u)d˞^r,ԣL#vSU/L%EyqnŽ;m^spuӎeUHDqg$"^;ZLtm`u;ր re]Q qi]o?d* h6{t9eGCt-:DyCbfAe \ 0&`-6n *M:NxF[',#}pd-6%U#źn4  uÜ\Q) Ne ]q8Y|s6-MOæȳ=!2$i R OU̓H>kM~]SPسv:2'v Qٌ{A[rȜFsO dL 0&3Ws.ѧ}NiK]0l] 1-@<8ULﰣ]B)̸eBi(bu.)¼[VCL1&*{=&ֵ>_0~r15pת29#hآ'ڌRarV R V b}/9mvAO;0WR錡T"Kue۽5O,3A-IOIb{ұ>Koc%fvM蕼`L 0U؍/%nFñp iW\ Lo l#Geyð5)Ӈ7r۵cX_Y[E 5\:\XsuMf'n|h=ZlhvDƼgBKd g1U{33eɝwTJrsES 9)Z6S޲{02_O'N#͓pq[t)eRb;bNNW = 2P=bvFkuAI'`)SW~ )C&t)&S~SATm" ʧP3^ݨ<ʺo0r}0&/ $چkB`E =9m`L 0&01q<";^ߴNnj(sLE%*+Pd1R`l n-Nl,4,([R"a(DnTDXKTX3QJ4[P$ߍV{&ơ#6QRzpd<5Hl4Ju"7R eB~a4C\fo( 2€+'rv<=埆J9 O{ mrtQ/svGBHM?9e\-.Y#~HSjG$GQs9?]彶|tНoC0]fFl ɆEcK'O ((5Fx0&`L`,.&kYE==8xiѨ^n#]MGmE~ :rջ`w@IRVؤՎ^8.1n ,TNvq⹘PR^ U MQw3+ ݼ#~O`gSE~X&9uޜ6{c+ms'r`L 0&qke0ϫ@@e7qw`L 01W_WG<J‚k~<&`L $&!Z;H$| 0&`L E1&`L 0fr`L 0&G3&`L \[X_[k{5` {{wdL 0&tj~y/.B^G/_cMJxc,S ywa_e$[,1 Ƙ^G zz _*,pib\N |JӁאu[bL 0&,PW?"J?ӫQGAAn*_ pK[Dβb@i&:{<_;kw"!h-ϧh)菶 =Hu!Yoh/!eRm`L 0&:(V{o~^旣m}^JE<1XSڼ>:ޙvt]_Xii}׾8Wo Jxrx'`L 0&pWWV"{8z@1~1 Zh1HNq_j*z* s? 7NC6MG]&`L \%IO{ Ura.t^WGڃτ;AزUkW"7MGp7Kda=J"+HѨK`=86k LK,GWVZ z:l)L3|ÀkV xZm ޷`u:mE.^vr3?QOͬ]?oBGa uM &pj1nYwڍM;U`mY&J+q`v'+ZJ~Yxy;Q][Zw?gq\b޷} ڈS͸P'NWS=܃^6܁>}<w SDNqaL 0&uB xspfku wqi}a,|b5Ҍ(#!M{_Þ7y)Ťq _w?^(W_@' S(F Ba1ՌgK%)WßL 0&^}}KʂrI)# Hk%@,D&,ޙ bw`O7XVÕk؉l:lOc< V xhym:9 B5;i CA$x._Ϳto1&`L`FhDSJdt]~'_c'Y.Eٺt,f{n.`aorqhE6ZyR+8>q0j֠ՙSiמӏaq8:JA5D%kp7Ufb"+B!aJ">߅g߃X6ԡ< " QZ`L \Q7v#KkI[p8C<|/ӛOQFY^>0lv VP Vֺ+n/ A)bKɔ'8 Vo'އ 6zIAl|#V~"kBIy1Tv44EM.W`L 06Gѵ3z~yաX#.RSeXY@!o_MT˵{`L 01?BxEȊE*Xm&`L \~ ޴5ᖲ0tî=qgߙ>`L 0&0X>sn 0&`L LfU_`L 0& `>C`Z&`L 0&h,MU#93L޶ e4?5͌޿਩Q `L 0&0{&6_^勋˗rq@jI%wݍ<=ǖ6d.ԽMnm:?Y%JǍ|kuh(a,LƜ Z})]bA- chx>Y/3!c&`L`fL(˫AeQj?ӫQGA IOtbaoN!zt@$).yBuW$;=RE4-EZ}qb{%7fMKỼ`L 0Ż<۴/GFVR(3NtijbN*z硲^uapZ s ,:p8҄;AزUkW"7b(v[9[ -R}*Z˾hؤ^kbg]ѥKPT]tX87mΆwjܻ؊cX*>Ǡlه͇UXvY!lZ49[VBu`/71xFM2҆5A7>ҞڍWJ~9黒|nt"S2w"@HIvq,o|z ?Cʡqj0` bi>\ۧ vȞ~ -Nį e 4fw |?oWн|}sa~e#ԡI\W1 ?ք85 /nb8R@dP'u[.ӶŒrvgX KfUߖfPo.n*5l ʛ:]T $,ţ7{BQ`9p;)8$..}7/[c*3JJLhDZNEq]OWW2s#> N9&&: Թ=p.@2Duvt)_m0ETgH)_22 <T\d1S8~ͫWmDsw7Zڏǿ ƇtO2bF>Z l/}⩂SW(kzz +pФ"nI}?CG: {ShjSTfZKYhpML#Ӽ`L#[P ቆl]pwضW(FvۖW?L ]Pvqh0C8Է[hAS ,Sd($BDEtv1nYwWwFx~X[Ҋr*'@ ؇k`roj%@xf&GQP'@IDATC#NUv}(.Df-0xwa\C8،ӉWe8?|Eн?1q3H wylrO Ļ"^ &nA_GhPxcd韄bɋ%׽To, N'7Pw,T*(ٿO! wbҿ:=E3h)3O1dX){O#pRa%ޥEbu2c-uD7FێT}.YMƻi"6: G> o1&. ql͒čq2$ťH32HPk}gMYePKux2C]P"uT ^'+V !K9Cl!>.d*kfF(hߡ׷N1l+a>}X,{k[E,yqJ&$.2$SB y4rO aNPj҃w6N&yTxI:T@FpF!`}~cHuRw_9ޤ;`8_A' ]:AMJk; Ϣ'}uhacOBa-I gJYo(rhL .Rq1$%=qAW~`| 4{@'.q-eL 0&p_`א+]l:^;}/orC6xť!ȢU]ܻނLVf,/- C]><$"UqjO!WPfӲN_YdL-8[ 0&G x룠Z#܄a"2|mP@V:v*,|aLH;qA,h +STaN8?=2T;m, wdHcnsX \IDb_A.F졅.:xH2QDclP1.Tq6p<7ȃ=xe;cs@)+5"[` E})іGH5wb`iiCג{12)ifgL 0&p}׈6r߉%wv}N&eaٺt,bZ@q!w9m/Bc-hEб 3nYjXrDm:7i.J1ǑhYP+OzQ\^ S>@9"SXw*3w%I+$l#Geyð5)È7P-ֺcEAG$1/#)YȔb*j zЙšk/l:Qebڍrj;kņRvܛpߚ9TCQ:ޓxcoSz\y'J!vN#WdR kJ)I!) -hɆ3s@P(LMΫZ]Ĩ㵓/hQOJNFx))/`FJ{ؾF?7?i/E󏿃9'[c͇m}n:^P%lUz" A 9(1N%l݄6}uH2&]5O.bq,H{jq<;pC^$I|TIO؆ 0&uB RUE ʎ7ƺuK1eCmA[(?`1rwb{ {JBLi]jQ*"PTMW8c~ Z\4I Y,3𘱇Ww(KsaV {᧤mJ[AQl=7+ťn4Í#7`"ǿwM-z^8dLXQDYSD>qQ(?!=Jhk'!U#mω,Ma\OoIkj61aE{ɓdrcW%T$I\W•2y-E{ l"ԅ*&nţ&Ɩx58IJ8F[-&`?k&ZyVhctlS{`ǵ$l"&{KYЩpvQ*X_d.cM;*'1 j[hQ1lHkR<J:Cc`%L,bBIy1Tv44M#)c*aoJtօin Ye+ _ފau|C$z%^.}bhP7FGA4Z GkuYvZOC:AuqwN(Nio%6L[ݘj׎#>EV;AXjIt11!&`-u;+:0+Qbyc*9^~ u^UZ:48⵻w=w2܋!fzj yj]&`L`XOX]h9 e4}=ep:%4}gwݓ5 i= 5#ICNhGh=QcfL 0&pE ~E{ȍ3&`L 0& ',Q1&`L 0&p`~`L 0&Ǽ(V_B$P⁛Hx'ٹ)"/$L$~ 0&`W][5 97ЛT]йΰw:?Y%\H9?& ͱݿ<86Ǖo61r+|n iӁڟCw3*X]^V\ h:ڌ(w`L 0&0 bͤo yDj mt|~pl'$8逩fe1o挣3BV;c[Hs—{rG ~$E=Jm OŢTGqf9zuJ]}H)oY3ђhњy 0&`G x@=쓿x|*z]*Er4A DW3 G깫0j_HFky*U.еD3Ɯo:7ҭŁICBͻo#72.!@`L 0Y&[r'攳4a?F.ZOns-\< Əa@oVcO]}fL 0&,>NZ s L~s;KmЅZ[6՗R}q>Sop4^b][ ׁݿ܌&ezk :'ܼ^{8. v7+C.mX QvJOFګG D 2_ÁBS⃱OL0B+ZY4m!ioxMWpnx@ .=vdO\)F`;MJ=&`L , yySOUcAyBhqȤ5ktQ,$;/.\S&Q!K`}2/cO, QƼyrTfX/ xZJJ)N>ɐh3]NUhq!CZCL%K-(t5z]7.3+cBZn*ߺR{T 'ѻ07ʚFoijwBqO^}Ļq aa.CSL:-?AJCp$޽r4o'ʯ;fo]D;`8A_ε ?_M&*œXoGHSQH"Ԏo< p^S Ɠ;0vtO1&`L`& L`u3+)$Kr\l&}x Dn&Qڑ,Tx0"wCbezH>18z Mn;.ϻ,3q G!{wƷ9>$nas&`L $|-7XHl_Lk  |'#1Qj7/P'p uVG]uJsE~paM/A l۳v\d4͙qOewFSL7Zr/zkmNi^! /!R΢d$o1$ȃaG1(l蘂$Nj?`L 0&p\>zhKQ6 0`NXCo2sh_RlFl;&D5'b{W*܈Q;"ܱfZUonR؋eʿa_].1ˡVq] %W<.V=5Õ]s(ΥǿWM>mO$l-dR))*',ROSͬtL+Ohx 0&`K`buL)<1YsG?^0UmACǠoyLzsdbsXu J2!7h6`L 0&fĞ7RǤ (A[Y`TiXP$7?6l="Kw6FÒ[R++-4Fj6IweeaX 4ýp;VFſ'1^[>lηyUdOQq~Y%I.swZŧc?2;łOt-ąBJwDP }`? r)(|~DԈ˜ȇ*+hR:q!B-KV*u8hحQ&`L `L 0&@a3A+fL 0&`L`~ow 0&`L G{|؊ 0&`L \q,ޯ-0&`L 0x[1&`L 0+N܁ ZkBk>`L 0@WxTYuF_};U>ڝǯF%Xbxkq_HD 01꾩| 6Tz/m=2HB_^>6.ӭfT:Noq5螤7y9HD] ,^QKUMND=:z"O$b|IׁԀpY(,$Mosu^ [_üz{h-4Hջ=pXCBs[8b87TKʃTg&f~@o)6L 0&UJ x'2LV P=CW%V!E 4Ļ]޽ >PQNq(ER%i~;ӨoH2ހ El!/yrʢ/HjQawZ /hеk0\ =I+uh(z8-=Mȶ;O5~U<8eڼs_Ae??B>vztI}9/?ddߣfyG޶;a}д>k p*F F%<9sXOȏO2& "G`^9Cg =[G#$7@E.}%Z#. [0! =z+< C( O%xybˮNT]\i~8ێc\"E#CRCT):78fa][ ׁݿ܌1cu3Ӂ~ !=K\yp}sp/ߕgH'$dv8? 8rxu/O.6*$}%yVw|y\z8]c[gH:` ҷ FA)x;r"aNLar4) `L 0&0u!=K+@ei&xttOS&*!Yn] 5%M:Fϰ4,-q @A6puPiVEIl{ʛ*`QQȌC*뿴f.%(4 xa?qa4,CH(n$ӸEga^+,bRQ'AK-d#̛-=X",LSUIT1]۠Ͻnt'^ѯ6`(N>ӄe A!<:tYh$r m5揩@vޛ'?HmlWfj}FetD]I'Lʢ )߱X{eĿhΓHݕТ_`L $e,}ش/LhF{شQF> e((ǁ6ۋ 9hZ[N42،B>|0w!,6a}*Ig|vGp SߎtvwE4<4AFCHr7Zu><|d?]X%o9nA_G*C >[ >vm{F#$™eۑIT}d3\K^4,& } *w {bU M}>K1CEsEoI?D-9gˣhmezG`L 0e>` sQ >ӊzo`x8LX JJq,qSdԆ߉B DzvN(zFŬ]*[C$E!@vTڊG K,\.x- EuQ)Ctik# B97e  f ux2C]~'~13Y=:M?M:z -R8+-RuI@oZ}fZ(-4FgU75c?ZmpjG=z2AҤaxre3xF'R~3,7#:2 q2R;R|X,;2w(f7mGHNvcL 0&f;iڠiܹ&=XRd>3I.AlS/lYjV0LY  ?cy[7jH} "%0y(7W:1@!=&"2 ;:-ULd|t6{]2w4U%MW$l7K0m bVςxP"?-ZCFGB picYUh۝aR"UqjeWh(=$Z=vdmP>m@~:\ ҋ3$~6zex9y< ](x% .L 0&D 蔌9 ZPmv$fݡl)%Er:ph6b#}bG+( 3nYjʢj&12TVLu*4ñc g)f<ӷ{źd x2Cp(2^8KpǚukUY{DԺSQ0u b}/\zXD,R=<b%C(RP?fz_2r8,F' 3s=VOX_nϭȥ ,cL 0&k"-<@.w3; >KILkJʅOiB^\cKɠw?x+ `ö& 2暡 H$xޛUi,[,Y&DZ%$ Yb $%l_[-S~W3ڏ,M!MRHHC $q!vU^d˒r,r$ymt߹{{Bf.Ye 5`k&_ꪓh 4v'J8Vi,[s "d l,^9JˌQPO Bp"w>e۸%Tbİ8 [[p։<+r_ ] xOyQ浸lQdhZ ©ꆠ]`ëLhUcVt\*l"eIȒ/زׯ)ɔ%(r Ԯt5y!\=Pw i ~-rUՏԚ~hQw wdh7^e YHHHcM@WRRrdΟt QO8~dysgHfg}"),gI\Mv k8ൺ)oؖ^MK%M{Kǣ6fˮdjDFcsQ1liw<}(/zX޳ 77þVd s#}/_;x:z(o;B%𥤠_~o#@lc74 L#Q a_|56.OCz-F .'#206遑$   gÄ͌sۣ̽Q|J_h\(CpHHHH`pݏ>^OnEjũ#P8 0L$@$@$@$0} PO߾gIHHHuq)VgVHHHH%il4 T$@>{u&   (ާe$@$@$@$@ST5֙HHHH`ZxF LESXg    iI}Zv;M$@$@$@$0 PO^cIHHH%il4 T$@>{u&   (ާe$@$@$@$@ST5֙HHHH`ZxF LESXg    iI}Zv;M$@$@$@$0 PO^cIHHH%il4 T$@>{u&   (ާe$@$@$@$@ST5֙HHHH`ZxF LESXg    iI 1V' 1x<4.֬#    1dZ,3@KfP & 3VRU{p>hz(ǹcHHHH.{=)N> Y"}jO4C"ޙHHHHH&@t8VΘBՒŒ x&E'h 1     "x/FnHx&     !0|ِd21G}$K%    PbaI$$%% |n06y\>^'p >H$@$@$@$@$0/o{ m.zOI}7q@$@$@$@$@a/f     "DU xm    ('qj$@$@$@$@$J`jyeۓ-۲[Mȶvm0i/|޸Cc   IG@/VT(4hXp#L> |fzdO,h3^4p2(ΖhXyL'w_DX:6#۷_ (|'|O; 5#lwcN   IB`ꎼ샱W}|<*ҩ #Dq</WeV7?F3ۭV:M$@$@$0i"kwƲ'|FyT@gœg __^&t?Lҿ?Jm0~!Vϭb $/!e T#0n:e=Iʫ^WQƠzM0hIi.־AmnΑd,wP%  zA=zE{%A'_&E[H mo~o'yɧ)2?oɀ|9/::fWz;W_ mSA[EkBt<6n}7Ժ lw9:6];w݁ 7? .뺟gخY,?8Sg u"onZףŤ 1[O=Cۼ u߀sf^o"ēys}jI><|j3*{BNƹW"R;4ujNRMk1+ ǥ۱H{|υ붵pz6t!f[菡/ 5vǐGG ])rO~,~}!{Bo?!:n]Fui'̇_^bnoHI$@$@$0Li:1ȧg'9/<]]dl,93Uk˘ywk=n:nm^'Ҝpʈ)=i<o!W,{=HkޗQue/I8*[d§Xo`}vlh Bo:"7jV^x 29 T/.*Ȥ]fɣd{]7C?鯸0 hQ%{ >%x ^Cl/Z ] 9@}ooEfTzV]>˸;TBD?IY/< y2jP($Jr,R>,+>59Oz5 ;pTn>yy*[u+W P 6sL !!PA\   IP7dvZP˰|V>ēTlxdD' ?u^ා:%ve in*,umu걮k*,݅&eXɫ-{cM+-^}3eL޽dz\,Tǖ7N+]֯]3ۆ{PSLH uZ{sڻv-0x1JG1f_7nK;D3   @`JOXOWѾOEG9hm+_Bw/^NBe;R<3М&1[[Dp~BV},t|tIn>RvR5E|j;p._\.kDMZŨ,@MaβMEZcM"~UBjQwkvДihūW4Ga&˲j<;FVduTVs)64YW+.HRa@SmX# i_u3ʤݰ>[OذESXVq6HHH`|G&er4]ZٌY Y)du6:k)VUy̇J??[ Sm= ⯩\F7td2vߏU 6Um ̍ڱĖF "В P/։pwK͌u!OBӆ!g#mKڽ5@IDATj)UdՙQ Ѿ#M`.e5Lo 돇~ps~U7{*dZ{w"MLѣ۴i/OPVc<j&dܬ- 8\#Khlxk#\$@$@$@Sd$S{Q-5nFFZ{5#y~E6_0ը:KxRןn|";G0.XsVqElޣVM r$@$@$@&+I )"Wx4+bm>|]4?u`#:ZhΈ] ./f?A⾚{Fb2Lsr3*A`'QO>aHHHlxwjCB fl e$0Xa=ևS{͛IHH`xwG<@ػ $~tg+2):2 xxǼ1$    "=l{*bIHHHH P LZS['C-7[e? TDqHeYjȶvLj}._1Yo,w V@m)/{K ǑMΏar1R׋GAA\ L5;ֱm ] (|a'yF{G^;$  ${>՗L@"4}*n&1Ź.2nˣt*1CS=⏳בIHH&y@k>22w¡~*0QS'/5(ãXF1gn׌‹,w5dv   '0n:e=I29ii~,k0Q.$9i.V^Nd? Z EtWWBtkRlPՏkv7܅ޫ)K@seH<3|*hh-Y}oF0?8/ t5ı/@onZףŤ ZO="}/;+Fէi/ }1#RǤ}{j;H /C}k\\HkqkiÔm / ?"֖N :2б.P+jρE˗ }'7P.LR0nHFMbZՉ>msVBe%_S{   x׵"'Qê hY38t'2v^ YaKOCp87]ʊѾ$o7P> 7_!#_;c)p(y!n/%V@wH"s&[1ohJO-w+SFRp~^qCG}-E͜yju!'8oGպRt)☛$@$@$@ӒcczF- 'mXAa[.NӻKг{En<ܰ*"WE+\A72*RqLͯREk^shWpW,S#@]w\׾7-j v d8On7&nOZd8=#*>%p*'~¶gڈkoo0Qi㒐E_~Ӟ6ZpPkǍaƾ$ZBj,_skeRDk' H4j't#hGH!fWZq~/KϠNWRv bdn̒G "MwuPkIS4"  8؋H3Cc0 8}@?\fMT&e|_]5g#"GLJ? pt 2%EjHڡyu¸ sUo4xQ]uJKD~ACn %:H|vK>+j aϢxҳǐ7B>)ޝ{f؋eP7t.[%!5!wlvu=ݪOŸgP317I]VtMnC\6܃2ez@J,}nPVߎe9FyWFIHHH >#aI28"82#eo!B:}>JQmOF=pHN֋WȪ4 QH@s+oMPP[$PJ`ckիQInȪ=;FVNuC2RzmhLz%y@w9\~X; hʱ =:YW݌s2iz7Ͻ5ԀGHHHH` |GuԵ#s*mP]Mb+a!ɎrԕmDm ɀ%dȄ@J8ey- pbƩC2G"|^ GLI %uVG-Ūp779]i#^@^=Fxۡ%yq}cMa5G憃ӭ{DB{߫c>nUgݦ("j5~:$19GYf27+;=pRߡM$@$@$@ JJJF2٤=YLbCׁRlr"2Yma!$ԦdN,~J1C\EҎ=R5aHFE⬑+\xRן:G6< gA!׋l}   xY%    R`KNFŋwWMA$@$@$@$@$pv Dyq>m}wo,C% vn4    ,g;9|Zk)CpHHHH6̄b:ucy$@$@$@$@$@!,j$@$@$@$@$@g@TU디}HHHHHBJJJC#lb-o^ze/n!. :aUVfJJHJ_ $    3Qux% t~On7f @|ƼgK<ӚHHHHH`L DM.@: @|՗4Y3Lk     1%UU{Mg$@$@$@$@$@#ϴ%    SŻ,"5"=h?=itF$@$@$@$@$^CZ 1S. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I=N`4'    "@>QY. I 1}5\EZԝ?.`|> .9!Iya$_$c v#Dܡ>I-`|'зS|ɵXS {/c"tmZ>qbyH851[x/\1zajmsHyxX|nD˚%h3=`l;~8d3,j ;MK0ʀΖ Tcj'xh Wt"#)}-o9'A$@$@$@$0{js,=uO-AsB3f ]Lv9"-p^hTXp#~=f\;|x'DnDé\71*>v¹i ԆlGن.]3/4)-pyP"95̾AZd ЉjP𚜰d4֛S6Ga Ya@ gt3 { iB$@$@$@g&YBTO Q;m%@gY%p^i藲;(1J=}NSWz5Z?Q jW?_6$@1 I>{L_'n !Va 9aEa^s;Ulx/s&SOČ25h1Ausv6o Y wGO5X_nF.x:=]܎rE#/`HHHH n{nN<%Ad!Q;zOP;b%ͻsHc;fT?+ۘ EQ$vi퓌g[B7w9̑rFzpPǑ\+1=Vi@I~ ] Zw%lZ$D!g~/ftlQoYMԡ<_^x#5$   !0=Blʓ1/jw*:4ŚO˞!)Q?#ב0k5 l$b 䉆4kA b·[j()wYAzsȌBk6BK!'dGܾ洄UׅpHHHH`<򞐤 wp7`.垠7/ u V7G5he>WkӞQߚQggX2G2/nOhz4hzч{>>uI JfmoxHHHH &gIex10J;_B6QLIiь7WL6y.e >e2^:G{}.%E.V@=ÅaRԈeτB ( YKD֩2rAMAk^(ˍǍoKK**:` LRmxPN{pz˸kQH`R3ףUNXnH;#߱GNMaHp=֞'HHHH!pf7^Y%γ %'Vdm0|Ү$ծ*nr}_wD?V |QyYIjd=X0X킹μ.!_V]@x/qu-,3>E5NxHHHH NQpy7&YJO!Uu;00ImMqldn6jJeu#wI'pe[@Ė/B':ӋnZ{SNKvy۩<]ڰ2| z̓.?4P e;ߡUR?|PJuDX4O?OVjzMV쐗1I}[*v)bO:viXBփHHHH`t¢ qǃyt.h$؅2jugpNޛvuڂ;ޓH{ p_ 0yAyIRhLЌ_hl?nN&~- 2OVȨ]&tZ ky7BM m\j7"h6MY(*廉'K&*w?%ݷzq^€e%z&    8xtjnOh yYh%w}azpK-Wߤ8ZI" 9ƙSm{䰕Օ].(-]?a>r#c挔`>C"羻`Akǽd FvAQ6nr6㼴V,_xday0r}DHgs[N!/_ʄ‡$@$@$@$?]IIIL/ݎWilJ̆|!ێ"`ظC/sS%!="@c{т|J|H 8<ԆGHHHHƗ㋄IHHHH`r&|rV"    L}:>N$@$@$@$0PObeIHHH3l; "@>%   (ާsd b!'MHHHHƚ_$.(E4䥨>\?jg+^Cbwemk݈ҵS}+Pj=.y/ۆïU0)WXkGVXv̂Qm__z=2_6P>VQo[(]W-GǁOmFe0i+Q?p$[ShZPFF ]7:Q) ,eU[E@G.oZ/R}hk7e29XaR_vU+ |\ 2$ ּؼkqIND/]6,R5}}LkA3r1lVxǥ=A FUHǪ!>Jc(VI z 3{Lv̟oC3_?HIk?^d3]}-rE[x/ R%p.%+U'd&nxݸG$@$@$@,͚STrWa*]xg$F\蚆>OPG}~/ 7O0jt4Ԡ Y22/umFULGbÚ5|l'~R=^X~-֕et٢mklۡTkrUWŸysV2k)b-`|H2ƒ6$@$@$@$0!+u_Tݩ1,EY~] 9m= $aGuD+-8& O.ͅ"kXӏyhh%1}.^e@4UVdz\L[)(Rqkㅮ5rFowE6V *79#>GHHHH`b }/DD`m7pQ0w>}G/XT2YWY %)A+{BW ķw#Ib/WPmfV7F[7UVLͷ|zO)7eL;3lDWrW3>V_ ؝$+'DmTb'WFz&o>j<#\+ҳv8g\+Q{`p'$}! 3w![WKTs O Q9,C}s17IHHH`V+X{Ƕٸby,%Y2P/5qz/EEH@υS cXn{&3*ض[W5e0Ґ#zdՙWwkF8cO>rYyr<|ɗ[pp3 w/v M #8ZeR`_0[S0=&$@$@$@$0t%%%ʵcvnjڈSUuCe& 'kh+ҥh!0 DMpW ͗/8^%Z$̩ep<(cU;m# cZ3 @6 MnbF+rpm   8>;LUNe%[:Zq;8T1LxHHHH`xd pY}8s'    (E @8a5N`4\!ev{e$zkr.9-z8G].cC?<%i7$B\}e \z!XfBѿ=UqSd,rO}Րj$$%aO'ρ`f|:x`Ȇ+_Am9I~!'dd X1 נf2f>cqoE$i}s;o~ y"3HcQwWyh[=^m{}QҀH&57beU NO"f3 s sm[Vuz/BSؔyGbr= [` Ϭ9huun2:ԸߎuH= 3=Н i24p{};twm딷C^8=[u:M\uiێ'ssT@e%<X2k](8`LKE$ 0xϕwpozX17$0%%$ݿcSnvoۅTAc.%Y'ڇd%hmQȏQ| Rhz1>C̙5(8IF5Ϩ]g=\׍@ z>1R=xls]@\mX$g˃h?]ȷUyY2# ̅a[Ok a{E?cx/ܕzawg@]d,S{o;UT#vq!.ܵr;EO؉_Ȧh93Ll=hLN0HN5kw.j`H8#|$@$01+];uݷWDž䟹j;_@N.v NWQBI;kcK=LFbԄ^o}I %9w|f1T_⭷ _{SRS@Tַt8oFN;*rNly/_Sw~ݲ ?DZ}ؼ#<(:.]#Χ62e&oYнi-:feW_R z +0ۉgQu۷>c[j^F7 dځ!%\t{RiG-~ac[-6}&w',oۋHZtBr&͉O,NEڬ\2V ~ wy0r.kΓ6zEx*ǝau_C'C-ya70ю@y`]5;+) =Ώz)I ۰-f 6d?6v+M~]؟ ~:e02GH<]}χtZKWIu3oA3W\'s%:H&fӄ `)}hk7e2%vpMH}WUA ha67#$ ּؼGҵظ$'XD|z}ǁŮN]Mٌ־dd5_ǠCڌ~Ň%n޲r2*oDz0}lHAو7^[kCb2Pٳgj'юm<4?\'t]HkqkiÔmCr<Մ^O ɀkY %:VfD,蓨UBLR/ ]y|(c7k둠OEwN%9Lchom8yia \y+Q~$X{)^K2 -{0QL2giZVN-h++erp<} H='D $, nzLp "r|;Q4aM'3G&f#K$O; :ObiH;5z%Wyp sb^Ԫ$GtrċTg':dDk3GW%- 0Mhʱv=)O<שZhYs&mH΅rfڤm+Y C? "sLV3%a\}R!φV0%8~/ L8]De\.3Nu`dk.4hr4Jzqӡ:&*pP.2gȲԡU2!LOlXs&ܛeT;*գb]Y&J,-Zؖ3f{v 3cb;^گ$ϿEȬެzv$D*;Ǯv̿Z\Z]Ļ#.FI.Ј5H}[HUʾ_Q3Gas&MQtߏ12JOgcȩp7DFC] yHLD"|bá wa J|?jD]yFInHusA]7Ȉnc8_~L<j7o>&7b9+}'a,8P|z1v ȹ 9EmW- zIA?Gr2&Fp8d<=5JҋEXs/O~Їw« IFIg+1\H9x9Ft΃S߉ e\92VTIˤj>oY(7_dwN"-z= u{հf8JCyw_4k>d%9P9?A=\L`za۠jȕQ%3b* P34=,1*W<_uKg܍ I)~gIxV6!oJ/Y֞~opȒG=]TN.De "vodvĢf&=г}+QbѲF@c18/r~XPk>%V_^ݑ"tE AE}e1Mğ5h͘>+eH;^q0EWRe- IY;_p^R_zyZ.K>{au l l?j9(c^b(]Ђ/R*g/葬hO2..#1^W\s,~x&Ӱ6+20<<͆Y{RnRKG"EXi?JxRM"%)[4 ^ s $"ժpPfGN0L3:E1F-JA;;aa8(LDp%dDQ!/4l'ڲԥql90V+YZAk'HK, ,(_v%Ǿ7P{FpR;ogJbOHB@zWR\\IlֺUz)TGFEvvȑXcUg.N/xګ ]*JberGdtV]O$? wHSIð~FL|gO2>*˃3GFIu ka{VFyj1.ُ:9Ek~k&LF? k!c W_!y+#O#ѮSV^-<ԡ[~zW*p"$]x~>H_ź2FBB"ID+*nL0Wz-_wr.j[bD$@@dޮ3&`V6ʂrEfȕu!X1(_6Sj;|f|Lh I==1uӱ u,vq@Fߗ!缋pm͕k~Z6WW:4%PoL$S _6[]^v3]lh@^rE>RQõ7H%~ʼ92x #|H&0f$>QEtC0>Reojt.-b^|%{i4Sb-L׋/^tHOno>@Y-KU'}|~e?(#CLfPqNxŵ[}"PCz3g"!?Q4 La~?UV򗯖ASGܖqf)BseQn $dp;HGյ2G4;>4XgD]LǢ{̣8N+ 64_҆ ~CGle"l5H؊qlUOhy1n뺵'5]3Gv=8l7HD奪Xiv!t!;gyBj\]-2p^ۊ Gpd?Rt-eW !Y&E1N#I#MR<T&U.]mDx0Zt{<joyeCGFQc{Dg~)le}:UWeQ/Epd:W&&˄s 1OiD4{坨Ar*9&;IڵT&67n +:!Qvu"sPa8z(;#xZ|ST,*AR~ ȫFrqN=CF(˯x 5nf!0~JSظyK6*|6v_ s96冹oGFFƦϮSQq'pl識%Tt|mGҮ*u\^RNKn<#X+RwJeu9wkC2AS̺f@m'ʊ3sy?w ձi,TuޓHT_U7\Yn7 $6cB?@fs i~3ڊx Rtw̍=+*˽WRf>ZY-cJlsT+':"OR &֍pnWGVu m%fZTGcގ`$a6@IDATbm9ʣ<\۫۠Ml\y8>3Lp<ǥ20R;͔I箣aJ~e(שXQ\ka*ޯφ#'ns(6-QU<2d&BIT򨠠ŶXǭjZoJm\[(JE P*6#I !dL&_kh~䛰+93$A9Iֈf#H*c~Xb1&9w9z2o` $atT8QLk,L9v{pIJr|V ='d 8q#`̮@)4qAT|%\WK w0͍0测0x rW,ۿM豗mk)"C%hkyHlhW2O:1#BxS64A.͠KWDZ M \NWI4T)WJ@ZM2>9|N)Jev'I厱o4SQGӔoʅ!4+oB{T Sjy8v2L^Κ1#0Cq)D8oq o7 l>LWNGܞĭVS?e-@_R@vdb0ЃӣN49 Ϙ/C0#s4ټRq#0#0#0-fCF`F`F8~1F`F`a!`D#0#0G ΂\, c@z>W;R_美sϽ5fv3yCyWgY;~RfYנol+@jqu 5r*tApSNd1=ۥE#y{0KݯRԡb`ڐk OCT+]xoۏ>j#@_ 7Z-Y+g) 8a˫xLcK>MXZkQ/z[Py`F8» cKQq&Uc6m"kDHhsgހՋ*>O&љ <ԯ]!E *~a$ҍ~K5yNf}jKjG@ퟻ m!PO{gE~*--n\$vT/F[ezrjkYs:zh*Ga8N"䤬F@WvD"xӍ K.\6;\ݍq?EE;8M@n頟%rzp&>Dg}~09Hҵ\\55M[W j܅S3NԶ}bJ3rW9met*~S_E)[[&t٨L-#McmfgNfsH]rYxF`b"!S-xГ=/CcX:\lpnrD؉ϐJۿ"$9Q䫊^D KpL碪kb5x{IpwbByn ,W΂l \K Zr]c$6= ,' $A<؍& fu7*>r7 /R2Ȃ{N&v :̕u6 h™[IJOfefb mzo8"Z˧)f/ȭKѶL"-v:>QmJh}Q/#Ep?m2.]R\[waɣBgfޕlZ~|dF`b#Wx=hwtA67@ϊ/` &κY=v1*x fdm.ZiW4" ~),<h ( . %]WYΛӥ&8/C»,TC)s`U؞YSJ>zĭ0q:d5tiR}y45!´+(\x;%U @fJl w<mv ?˧*\ 2j "ZB)߶ qs__'/t@"m(beGIڽ.Ƚ ۳a$6vM y`F${$. WχŚOB Ь*.@EQ.yp`^m|S-Qeѿ~dgRI2 tw@ d&j yt ybd |b4K= NB3L_XeNѷ/w@r[ZID,oȒk_c [-mCk;`:̏DhD0#ʯO[Q ;R.YwޅK6?Jn_+ȭDʹ"f 7QsOX~2U!Tt9ԘZ;sꐰjMj!R Θp bkmMR)T MӄGhZ@N"X(}᎟!? \aXİ}b-kn *tAa̐ZU>^P D?>++\ נm#[u'-&gQ)/8+galmzdrȃ7h dNWҨ4쯞q4; [&t}f`6JUp?L軼Qe#sMP)d|䡸V:%E|鹋Lga}g+Lg82Qg1WӀQ8Jq`F)JiRԎ>~OdbC!2uޖF8Ⴉp<&䑪}'I2hm"VI–#%ō Q=g&ztM9vahŁa 2'sote.ܤkL K4oep~Y--ǞV5?ZUk8NF&Vq8 #?Ed޽2yB]yZKMy|kZPೣV~Z'`UA՞AtFZ~R&4H[Nf/H&/}hׂ`>:y0}sY%}(]2 [쪼`Ȕj8d'f75L`*|SD D:ij; dY ™@&KJ1 P>+i4Y \K}U1y<Ĩ"35:MM^eGF`F` [xOA^آ FN䇽a6eBs -J,"~rP"Qbd"Qd8#[*RB܉'J2a1-f8SP]E0ݢdQ "HM&X%)s#5H*IH7'ɃL:1'R:v7:u-ad #JOׁ2! a KK =CByHS͍۠ɂKBX5FO#K%J; @ʞy :'|@u3v姑v-L 0ʗa?a¢$&QxafjZX#Q0YJ`,/8+KPh_scZ(W}WMQJ* ^#0#)´|$ 9jX)GG1Li €\Kφ sǐ-6.=*/{p)䟹/-%W.l%3E@eg(B~5+wQ /2>8˖>t7T˗A #e#O@B-/1s4#."ӷ9d;]JZ)QE~bħ#0&~I(8eMŠkgHJ$O39sL!JI?E& /%M]lCCqn_sX%rQbϤEQ&p0tǟwFx K4 h]|]7SJo@f$>qSIEf+n X$v%NϢ)Н+roLv(<$Gߏ>d kѾp;hBz^56D\TSwR1ƄwQ}&Խu?E,H 8;Y.+4J*h) O[}?v}h_fTRq|pUP7i g7_(`BGޙC 8 (QpCig%Bny,:EG)#0#0"KqX#׎6iŷ+v%}7 X'0bO4&>k>NrU`oѝfsUtJs tm,J&anߏ'_xBQ],@+_c˰|E7 }؎# Dtϫ&]+P7ej -[YMcV#!{ ¶[K@Q@Umكk7*Ap:oX."8\Jw31 d[zdVtL2GS/Ko™ f:/BUI#@f  dv*jĽT Y~ $,Iw7Y _ާ6q!mTe&7sa[Ċ7CLuoAۼbb`̂s!-1HPUZ߰<݂21TqЦUsULt4 ]|ȓ,->Vcܗ| 6iH; rO֠T}<eG$h }D"$ 0#hA JڌNbŖ'_{B=~s Ndx}On? FKd0~ 8kW-%t-h.6)Do+Q{ Y-:h?Xn5+G4yߍ -jWjxIA Gw\  S.y" bC7yU>--0F=pK!K&O]!KVl#OP07e0 ۤI:T1 r9 >AFaD*ONu ݋^ȒqÖ`P\-jb) ekB^oQr_EZq6Cg=Dqْ`Ml`jK\kD[7Fq5 Xb%WzZ.+1u՟x qۏ` R MH)Rhr yn5F`F ɓ'+%eǤ Mhph| F/zm #gI0ѽFK3`m@0!x7lh vey2hѠKpF2x :fnxMd3}66Oۤc4!0s2TWށ1EA^FR/ FN6^jZBmOg`"xʳ5#:;xB0ƑU6W{9K+|զ8L@C؂AώvF`#} n=F(,0#0#D Gd%iC?/gąbF`F#TF^وm#6ϸ Å`F`FlfT`e#0#0@:FI?k0#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DtɼF`F`QDQY3#0#0DYS o+j4kMt#0#0#4YXbv%,;:lap`F`Fe Q- ^;G0{F`F`F ~UnF`F`F!|jN&$ hYs`F`F`@rq ^*]fsS)9[F`F`F@rᝬۛۄGcF`F`}Jex!s`F`F`@SV"ll}SHΕ`F`F`-L`4d ###HPeF`F`F8&OHyg8w~RMF`F`F%jރuDmD Vp`F`F8$Q1T.?s]<Ώ`F`F`T 0F,khC=̨`F`F8$T?ΐ`F`F@\WAbG7 Xu]p@IO`E|%0#0#0@r;AK3`&l3X!?φ3`F`F`"H*2OF`F`F|!A~2#0#0@8,qF`F`FF pF`F`p>{rQγ«wsg-ˢ/\ VVӹzz.y1G #D Yw/J_q&44JSjkTh4 E|b]GoCS!4TYan|k7u F߸M|:KZU訙.rmd2Җ1汧Aѡ:Ջ0hgat%W<އK+= NjY)52}3<ㆵ]=2F4rP߈ƒFMIuYL#2wahWfy8E+2iKxo2 QLLol/EIޮ עz,=mel\iW y`F ޿T~: D\O\0uI7 ,.=0z}Ra#GzVנMANO72,pp]w7uE,W $xzp&>D mI +1"= 4 '>$Y c.@fq.P%Vqp]6]Aπ.UVLzYENW]-8U)O{XhQ 1nĒ6s ڣGw-Z颲Jz꯹ f=cayiKr21I =d㢶vE 褨 j˶bV}e߁)&FtIkZ頩?]0#x߿ĩ.!{ &v J"kPY2Q>/\!(O2OfYGtԄ;aټW\8k33HS# 9o`쓤Cǿ݂s%n2k<<1b)OCj~¥7F*Akw7!Nίpmh^u9S10:JV`FܝuHgT*[׬?r~ b:".Au`+[`ȖL el4KUJZ4m˄./3niېKLr0^g5mvEŗKiJW=EU0mJ|҉ܴKwF`b#!w$m{F2VYk욞$YQ.u0uԁ 3jZ0qTD- |YghH-&\dRL8Nhԩ8r0$qxxm%u_pV=Q-9Gd_s;%l{ dKMUBw9Mb_=I~ >}[&t}f1g*i} 7Fd̚@~CZ8mrݐk)_*V:F+u 0GlۣtH2> HMyJJ/ALga}g+L6y,V{ݥ؄ih^ _~ ʢTڕ~(x9mըw?Nz,a!eZJw8#0 tw0OM)B5絲^p;[&֞ +ܭ # Mg$T_p ,]KƧhC"֏דٍ,َ Mp$ϠlYX Щ©#8n( ~ KD3*=YܻBZ'7naڣ䚒L/ʞy &Z@[~Z+?k%ZPڱbRߟf)Or#PTsp4MyR5嫱P|WRPG +INyQ/\GgQ=vA5Ixr}6Hxi %Q0YJ`,/8++Rh_sc 3$\\% Y4Ή}4 ve_ϒ`ds WkZ"F`t#tf{#sL_=Pn!gU^M~م I#ͭA"g×,-g~G >e./*'tHfs"Q'T%X@& l&$x0FB4Xn0Ǽx,TO%{jlD}RW@0akR-VnjIX߄ۿdC|Pxאoxyf.*!d|p-}xx]q`HzWJim[ïɠY@ EZ] F'ɳQWTBD3I{8U&`pb"ԇ?v_oB'=Gs_PBz`8s ipḫEvJܞ5/Y|`F`$|ĚVbϮ>KݍULׯh+`D ka ;z 3%mu,8%7ٷG'H/so\H)$ǒ#3ɯ{Y)z͇6q$yz&ꨫm)dE_U$ҳTLK֟-r"n4Da2y"r968xԒoREu"cXNTSglB );%mvErpF[Ҩn2h[R|$ --x=X 2gnƈ(fڮ'Wu`em2Cu4c+[kmFK!gFmݱكwq#w0¼<-=;gd_&gaүv|s? $ӑSaч%4x*H3^<Z2P祥Ere 5rk( ܃%~Mh:gD>% UpR( .azVATT/@O ,*cbu.G ՚F:; ٯ +pyVd|pUPXv E!&]8M|eZ*W!'禭=n2r.ED?[ԫQkgvQCv%TICܞ/Vr.U3'vQ4F`h ؉=oxs~ ovdwvgF\_cG|7 [ٵ!i]_WC.]EFà/A/qS&otqB@%-Vk[۶08#2˖/Guh2_2_>I2&#L0Y? 8  bsa[Ch$Sa^`Nޕ_Dl|x[u0yH9P^&6JjAۼb/\C30oܙbȜ:LZ ףehŘGH#pFAV_o™ fzNX=櫕NK&%S0ڤKU`5O&K/mFh- K>F]Ksڞë-Ͷ1L촶+Gobj*F#߄}_7O@|ecC 5_tⅳ!QՀ߽ )&bF#G\x'-aY^S(~EwbZ)Y[ji PgPxj'dOߌSOȰQ!/jƗ*E($pie͑tpm~-LK=b$Kz/ D33zw3( `)䐹)?|/Zh촥C%P(T=N5\/Ynp\>;_ 4 DM&M(m*U6줧y:Pg`VfB7Ǵ7`L:B{rzQγC*i|RI6R_*L;NJq:WOV1Zt+Tg VF]6*}9P3q,E7[Xjg \E?OmJ۹ΦO=2 s(]i/ÏW0dи֍Nԣ'*N;> [ W΄f=5}Urq h*6V_ƿp:j,!}y[i$ϊ5kOĞFa!iaJypyop9뷄cu¯1՘oxZlV/ g1|?`¶>|K0^!tS yhg=6nx7gzml"ouQ~\96k#znDmGy!?gR=~soFeC/7ڽho^_3l![oBF7!@<!a 1~SHt؉ʫ;o$,;X>7=3+ч|JzztUu zuuLr$q!Ӥҏ=]LŸ'Cj>IQ/npi[0_r'^GD[of%P*/"}F|ԪqP3~}55Wx߇Ldf? t -TB`8xr4Ì0/Y ^qR9#Sf g蕥X80 tVUuT»"tRG%h-qz&!bЄ.[.\Sˆ-~f:c5^)oooޒb~p&>>?)=c WPK%: ᦍ<|:5"kWA?w>5=R9=ʧsÅw|e6딄 ʗF\L3?^] `LT >h+cq/ѥG*-B߃@spA҈=d+%++NQ?SJڻvpm4-—j#_yh>Lz?0 }5. 0p־Qv-mluJtP1w+Z?KU)W֨KAk؏^;pHoU鋸h\S`"+U C R0⒰kGbIav}ʗ@s tE}qn_ȗCSk~U;N=C 0fK&y! 0S'IDATYwѣ&7&ɛQ2}%O{ f~z5gB5+DLLp..CfېKWF?h?~2Sw'J!̇Q٘KoP˧h{Yɷ1i߳Ld'iGIǏ tN1ߚr 3wO>i[edT%M)eK6IpwbB!!\+gUY倌ùֵ{y}R&h-DK }39IxO~*{S?}yDŽw_FΖ`)5u(ߜ70Mliǿ݂Zdr2,yQ Bgf_CMZ4/Q;֎S7b}'LNҶgvTW)NU?W}`&!O mIH]gF"$78 xCĦsRYq.F>N|H]sjېk&:,Gg G)B&OwJKds O #L/إ-5_])<h$=L>i-V@O7VzVȪ{4 t[$ hb&dMk-w4]&V_fzBg84OLo0m؉k[0 }ӞE+Ʉogx/{ *1H:NG'њ:!C?%K}U?JH, MR!=܌LzQC;6]'Ȝbx&_ \LqwDbl3G .9^XߏD.`&SYs{l/qNNkVSLAb!tml N `Ȕ{^lh+)2xG%sЫW/05' L} W,7 ۛ7|\܍?GS0 4ttLA߭9&[GMU"eK^_-J'Ll7_AwFL;Cj)z>uVo h /`p\9/¶6܆\U.l޹g}ȫ{O~EʵhU nA>?ʁ4ev}sDY-w4u9\JGim;D+BT׃&~4f d ;#sadi N7<޻,!sC0g!k0@f +$e$d/6%0 eP6z~0_C27݆C{M>|96Ǖd^%&6GaBe8=ԞoӼzjOF"nl" +.y?'΄KB f:IUG?s= ]'3^@984ؾG0(ۉLRNK2υf=Lʬ%Pi/^3E60vD"㜜~o>dwPNKzA}i9(qj?ZX:hj +H5` $h>M3kp64-GឆG>_R7ސCpL.&[X9MudTlaGev|4J]lIZo`NjFZT1A\+ 7)2FR醥vh <,JnyS.pg]5c ])*S4 d+ڞ+=Dyc6l J; -YOXҹg#dz[aF#sh~Dqꯞ$ N'b ]_+lI q/%F$-|Œd ٴ}xfQ?P^xyތ.Iplߤtx4q.\fȕ*(,ı?[n|*}MCIHZqH ލ)`W,C)r&i7RIą/ >Wm鱙h}W75Y(h^~2F6q.Cc]y`p"@Z.lT)|q 1U$/6 @;-‡m6rDEO-6#t܂ F0a> t4}!םًbo_x9i @q RgjmzQ*ZGNԯ zH^]LzG ~KgI5s{SV }(]Y:>> sF֫wisJvއ;.+5𞬾Jؑz\d+qF f:Gt]"8o/]!S^m0l=s e z!w,@.Yqj(/%_ĉWVA&=;Xd[ ɋևpKB ww*U?![9kDΣߠŜ7-,%mL~[rybrNԧAҴ^CҀH/?.Ғ6L~9l( ~|%r(ؽ4BU˜؏Z2Ԟ%phjtQW)Dc^U@Y  hj#AZGWh|$ e T˜Cg7vҸW?t꯬h~qe 1rP?P;w!%p"m\&2sfդyiD\'a:0J3~FRPҽ2yB<]yZKDR O\\m 0ヵOOcFs0N7n1,sAHޏK`Ko֔ytmгTy4(TmۉPW7($L/míOYjj"&'JIp7uF%+ZhMhw!Sk?oq!& 4&RљL3tZΨ2GZ3PFS3ףu1N&?R<EقA]i{]N1:RF}Itڏo߃ww7| _(4†I㵔h>cҙȡ2uSF⑒iMfb/FʛRƛjM됳\W~9[eW^$%.ڭT%Va}(~6PWrHx%Eq".ֿ#wU3Js$0Aܴ? m q]PpM)Y5~$B(.],P4@1/™|`654 C i 45B\:~`{g2R!&lٙmĘ0;tq0YAY_q4Wr91@lٞ%!݄gօȄh~-W?G<#2uׄV!Z.'@3Iˇ. G_au/ټb >Gwdi0W̄rMWLq/*Z$yRՁ,QP/h=~D~E:P i](8Hk/fs,b/]y&?Kizu z[0אˆ,%>/y8E>{碐lcw4< WA|Nl*ʨ=GϩVU7$owði\8hn!Y.Ba`VwvSp(YVYE9!.u^xT|*q_Ba]yRbfFwP^}O2Hc҇Go vkRZ~w?)Ob>uOAFD`Xg77Qe^N!3E H٤4u:]5=>0f6#G ߏtEDERvwU=t]HWlƓku%fρ>Yn 䪹ovxtP;;a3p3.M/n]@(v#,d^v㓴Hh ϱBG1gד+7Z t_0W4?[/-<Co9\A77T#MKfh03JQvԭW8IP>֝ڋ.JI( r{ .G<~aDϖu4rWJeYi"qES:~WI~OK&B…#!sTbsD50yH1}&0Xιf{\v8mXsimhOŸIp{=m)aVm0!TQ<4gGlE%E wNW v\4[=6*&ikGu|̯EB& j k ȓ6t˿\8֬Pf[O_IŰK䋞<4'sv"cd'62D'v)OQVmNaZg!iIo3&9k CM$Ul4QŖ|{h0۰N&~ QsޥJ",aC߅O;yoj>#ӫ)oҞ%eisK.Y>ПLPG-oxOjH~1Yw4=y_Fxޏ9/1v+-=quAk9o1˃>%D;ZIf1+|sg+4$}'1R %vy]"uU.jH^3?B'e۴qTFFP? #T҂Ak"~Aހsu n_$H6޲6iE&Ҹӹt1-|tEa@1PBB+~|&{AŘ}wE` X`D j@](-4p5~Z6I?˱Soׂ?C]3~h'ռfn{U[_E4:wAb0ʐ?hqf3|Lnioc|Ek3wC&\EѶr,. [+Ǡ;J/]&71۴,U\$A]FutYBn4'|iu+/u籸f3C /)c+H^k|ׯ49oW4>k_GKs\?6:|ۛniOUg}E ZSQc,7~ѷei3~Ͻ98>ښ) ͪɣf=c/hNI[wZ3f8p>[%լ(nI>8{FzQB_\ iWg7^(s8>sOZc(cs.&UfJq>N;m<|9޿_e thE)H4-A%a.?mG;Dxnd~*;z.Fɑ`_Z<:=,'z0{GAq1Cdl\tVǿM3o%:Y,-ق˹)ޚw ߞ7Q-aǼW^U_5:_ehO #: :R&CI`a#(/40ʶsCЖ3_N&3P$u0;V&T[C)BrK;gP`ݕ8E SsϏ6.Fk$砇![ˀT˗ʐuS%̀&wnse"rh,I*e::s#6j㌙G.#Լ+M g`wKdԞl;%lC/#HmynFr/b˿;ˤ^,zmY/Ÿ|>mjS_t}sT<(46an 4C"`ν<ДfK=9ݯ^2wXی Ht,|h ͇J)'4[~ 3sr4붵l{OL%f2=[Y/o~f7*O-JƍӞ7dNGy` +QMѣW`a?`]w?n1~ZN/vVzq&R.UnOj+I+V'1_D)+}_3m9k]Nk 7^D"|Mo$NIfc7`ߙ9G_57dtXt#7di9~p{cj/Gؘtm`6˪C8Բo؁\=[G)nANc"ZL+nA>p4Ys72qu3c߳pnֳOp!Ӹ>K0BcrNՈ{sG^:ҫ=qsk?y ?\(Of!>|p^<{G]c};5F3_2}jJk9=ދMwr4['ebtx&PѼ 9}.P?>]u?=nDћQ {䗿eEM7F7V (| |4]MGU;pvJ"}m%^n㡏" Y HaD"fz㓿?t Q܍LSy8s[w(}:J^~.&ܡZiƨ䕀qsF-s>&H:dɫ1xUTaluzNY$UL6i,&IХ" " " " "  =ULKؼW"F$" " " " "0:ru^lXS]GfTAD@D@D@D@rӾjlE5xWP(=z:-d@GXN{W_kRsٹ1*ED@D@D@D@pVm ߱3Rm\QD@D@D@D@M E adw!uSy" " " " " 6P>tv>d@F彷oЬӜ`eD@D@D@D@D@l|555qw1HG݁ȸa;Vs"2e߼ˊ -` F {2Lo[D@D@D@D@D`J dT;{sK` F ~v3HSqY7-" " " " "02*{nI*h ByUwӵ(@Q:ي+" " " " "0w:,G O݇` FE&o[D@D@D@D@D`zdyr%UD@D@D@D@D#)))3E^劀GR=Sv)Rg{" " " " "0Sy+" " " "  HyLE@D@D@D@D`ARژ$IENDB`golang-github-appleboy-gin-jwt-2.9.1/screenshot/refresh_token.png000066400000000000000000003044541445400776100252030ustar00rootroot00000000000000PNG  IHDR` K|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 864 469 8@IDATx `TŽ?k, ndDnA1 ( *p^r*ZZJz6,T` Ũ@XB <$k=dgs|fw;L&p8G#p8ݎ@nρgp8G#p8\ #p8G#> ~1wA ^ xN!3crV&##|ՁЮXV>?:ռDW[;kNxW醫cS]*ʫ/{Y^_2y?I#t&{sAyd`mqwR-ك'}sOx08V=4f`c 01)҉mEobK=59$Dk x 3Ľ#ޮ`F݀}U:L{ B<(4\l)r3Sb""l+ L|X0NǦcx|Vb>z5N'@V~7^b$t.+8* 6`&W ܥ{]o=W_f L[3LzO=wSJquYXRo{9)hS))x4,y#w8];"ӠS0nӵtC)3?BoonDM7؞ ݀ f!h FK>6Hc5@e?݀k&[jڳ}|A=#XFj3h,,>V: ,\< a_6e|hGbτI{[F A/椡C,Y1Z:  ʗUa)8k9wE9FHg֣%1He{6,&Uȑ Zv5Ǥ m,8XV_ "[l0Ąxʧ-^|@&H7A< ikC}H Ȝ-ET lYx%X] " f,|e6,ZcWM;4! J|܍ʗz @_I'ov ?y7I=ܶRʆoQk;M| wގc4=v.)jpuW.OZkDjp3 =bM)0c0at2]zŸD@? SLi5t%j?SwF85k_Y9>hMv!㋟@X FLqò8`4"d0~I=#~>;%*_[a2K"~h;.ȥ_mpY5t^\t3E[V`Þ.1tI>{mWgaP}BPyp3^,U(О,R%xr[[}r͔?BMH¨}U}ذj@t=rR<N ~d:^PZ+>SaemK-C)7ݖm5dD,YC34[r<&hA:6E鈥0{wQrC[ٝ(=UV/{+HַxqG+œhT؇ qkb Z X:o=p޶9ƢYKo'ֿ€8+V;dsBnAVVmߖ``yM4b񧘪FU*ljhgJEz᧠_D@)^vm86LpIM)1fXROǒsFCzͺҽxi&i.Vn4iIIvldM%-Z*}+>5ۋ#EʷGXA/xrH o>a_qB]&.źm-Tt2uҴNA##uŻ>Pa1,lt\r1w)fMJA4o7_F^*]7+fڈSbͦ}YtH -3Y/8|؉>rd-X&DUHm8u+6ENKSi[Ϯ>&F*Jy8JP/.5"((`=D Cqr?`mfR4YKs+.oځ\~@&a4#²,ڌ7 Ss& 5tKg$#X헱I$X%Ra}g s K<蓘1g!<Ԇ_Dnة|ׇ V.EJX)L<1aqb,f6fʗHCl44"$>Ћڊ-'s+;3=D,8s1c eLR~2#RKea#-WM5bƍ4;Fh&0KwC-r2z&'YKh[Z jC5T C3Y !H5Вd.F(8+ΠE3 FR~/H6e]B(?F`W%jb>cW"k"墎p= IH&Rk\u?bK0vv:,{Q1ň4;eKr/HADlBBCDD`.q L O*/?UQp].T+2?A"(n= T{J?!'GUQQgmȦ˘gB;0T 4 {?5NT7RӞAUW@7XPCbh#XPOn0ى0PD(~ᗹ;Z_ V*6, Faĭ'JPTta4GY+QM%s)ɁZ:Jxj"IB#>{k ri*<ͣa\)',O{qو4݆imY6JN |QCzc 5(-*"~e'N@j&E@.Sa!2*{|D=OՈ Y WsxL|p.ʗՂ7R !̰,bTT7mH7 4CTPDBz:,x&ѳ( PAu?o_QT.ʗ@FV1 UVKRkMZA]6/y]{3a=14aRMG,>sƱ}я/lZiڡM~ugQ a D(X+y irmz,|TT*_'`&g!o)`;KUgAhj\͏~)gᙩӒ7jȂbzDX*NvaKJ7&a$qH 6F7/zS Â>%S9ZSA'Warɂ\Iu폾>ە^;),-i{ϭQ5^~BWmQ?}˷h˻8>"S0{k˸WX‰/s8Ws i""YDbX|}$CNwW%Y%S;9'#gJ@[dUK˼K_yӆKTgցP~w6 4$ haukc5ΝoM(.;= 6Rƭc熏1i<ҙ; E6 KBbԴv"h l)+_$Vf r5dQ C3X7;vB ~DŝarZLZ|QYhI W.*Š\Ֆz--ണ}BG7Sd+;$?ہ! Sh:ǔ/VzW!7Bpـs"y _r!b$N_E=~4cj͟~)'VbFh.+q>$)MͯqYX2l+{'ńFє :)/L`ὼևvȸr9#WJ۠b2O\U>qao5e}~l "1(ʪm*sYj'gWu%RpV`k6yQNӧ PZn{HGq֯qBP#*"yW~{b|}[+l CDH.=h)_%KLZ,:YZ+bq}ˢ%on114sv+J $}_NDy }`_Fl^_\7(xWL/Yh+T!SiBcj|XnwTWM?RW.[J[eYRbii0K=G{h)ؚwާj+ď|-۶x&Ksf*.jXSQ~ՌԖBtׂ>w cCQ9Р~e}Ǡ_MxO=qUdNdK: }XRRܲYn~{v/҂s -3C:CH ,)=!wXV[=Sv? m l#k!㶛i_q9{S懲ITϤ|1a&x0!zD: Zr=Cs_kE7zal߂K1ɔ+>Z{^'p8PSɬXڷD#'VLr#K9%sXWО_CK?,­3&"2f"-srp*Z7=(9B9/IN#~${W^۩s|^ YH+|Sv ҇ͅ$~< /w!J7黬;Ep~)֯|e^+?%_Z^Is`aQypgиs8++WqD7ѲgŸeᚮ2.HK_ Ѵr8?2ě̳roP 췛0yxSj`הH?OMO$?w_Tƶ[>Z4u2ي}wdY(y^X7h:&aEnRvG\}ryȒspcW \)doK3&Zf2U6kA{u|k]5zS/eδoXaDڅYC0T o!;F >/@Aϊ+t7-ׇ _<}2 }~%pEEWs';vΛ-Le?Gb*גlxiĸT#ɉbp#I#+8)da*$)YDAyn=Oή( 8=}M8l]ј,n@+pLUZ"-\MPRՌaiԲKY@%vXdb02%I( ~[~7{ųFIJYA =/^EmfH 1WW*A/w+X;՞<ONğ-M/8^7hط M- ǜ1GpkZY19/_D{X$^KhUB4>f׭ĹցHbNcº)/ ̘Gu`9qjϣ=(֟J|U#F?'h)^S.yG0G`mCOSlm(=ٴG-rmՄiiG2tvnoр*i hea>c&:^G|hb,gzDV!T. X_L:\m~g+~JKE5XB^K'[{v0Z\Z^[[Ӈ +\' ##6>CQaNՇےzi),Ɇ3{.wHM߱/#g!3tN9{O&S3mW^ $8/(r0P?XRV@-ZKն{g% ^7T?7_ I)Y S:fiBK|Uz1eOr+U3~V;NT3r' Sڏš8Sǰ!8 Cw%B[ڃDK$ñxr(hiObBF,Wjqr4)fNG<^7_HmM,֒8"򀘏j"D'!3{3i>m>nz۪ߡjjS31l('.t W %GTׯ[?)A6H;Z]6<;&1Vvry ezl/޹;w𾌴5%SEC40ٞ}:y6,XͅY9Kp_Th/c`i^@үQ^UFLd &B|ia_m 7oD^BW;,54bߙaJD$׮0Mٿw<6FT~rdU>?q]y.8}h!o$XU{c8TkOA5oUyQ!42+{2aNi0#gǙꕇzw{2}֯dV5~V9NT3R =CyƦ3rHJ2Kod:jڱ1)FP;/ IH`9wKm=sC{{VLA~&'1:`?>C&ЅHb64xvvD#TE<"OMٔ w_Enʿ'fFQN*,HgLEjW>tFzO=$D!(u8]ߋcdM&~D[]1z)< `4h@ڰ8jSm8E< ]ƴyԽ/Kvp ETN~) T訽4x~bs5۞݋0בD^Bԡy7uKCu%.վ_nvRC(*ڽ9eBERv|Sg/'/Fr,W@7uR1Ω Gp%( w,?'_u,;l|0^?6ʘ@(ZUqh'! cjI̹,t_0Xv`rb_p8݊'G{#gԂ3G Sɚ|<ߑgGaZjt l[ fsǶ=_ΜzJ,`ÙGKųp8^B6+V=<[G# T<3N[,orlWs UTgQ-x??'LƯ8G#p8}(9\4CS~%$> v/Op8G#p8.B/9G#p8G p, 0r&G#p8G7=z(^/F7I^gfdee}4udHɄck:ϓ#p::e`I;Oۨ<͌KElWkyh}7ҵ.tX|PF8y>ODzZlz~C*:i-Rl.D7~spS?EA)`,ݰw=LWK_3`V(O.zh>r}e5GcњFMv~p8G#pM#Q ts1s&f8S><y(5)`2YJbAd OyT?I,3F8]fmMh _Y tI Kcq+ N[Zhm+Ni.:D ?Z9Au-F(VSYKbNK?Ēu=7 L0zL Bq A0o|G?yY/RT5ɷןvu@? sHQꛔ{B9G#{tTHaâoƒsymkxՆf&L'_epk؇ CZfrxQZ-> Wfhc' }4[^cs VZ9iѨ*灍7u>KWu{xnhԙ0}X^P:ƻC`-olƐBQ5de!G 8P-Ӻy#mkCIj,JJzTIUo@G}DR?zfٹ(a]tV^sNį8G#A(za7!7@'WyR(S&X.SXډs O5">6+R*_tZ^(wvTRo)pȌ. W2o(" e&,J 8Lg {}nk?wʵ/eG}wM^$"~mZkٱԽ:w8G#@?Z@ FR[bF|M0ՆEl˜pĔ_ i܅YEH&j؅灸t]3_}tBhP7kYKhXZ ݶݵ C3Y ϑ"FZnXA u؈HĘ2obKgn}&-IMK3YV%nDUK8xd> uCvi9qֆln`ܳNa#4#RKe@0裵IoVk+BHI3+Vy, 78Lc'og'Z ]BꜴh1huPK'rU7,TPjSL3d2gТC L#i_TןV@7(oa]cǎ}C#<0 &#AcaH#.a@1 PRvG9|eG#|p1lgӳc3kilkwTugfY葓qlX /!쑨;r&㙩 v?. BFR>ePeq,Ufb>Pw};P„Dg0x s1]PQ4Q4d-\M;1m-ge|UlYBꢳ0ʢ'JPTtad!(q"[X|U~ ϿOL o)`➵N=(.O|̞fĚ{k rzA`0. FR,DYϿ­z;|3KEZy{~Lᕗf ː&5TC-\)*k 51p݇ʗՂ7R!̰,+R7,_F<]Tb;7Uyx<{}Rrb#IʂWhR@,aktS9|d!/G#pxTB€5;p9 g"ԍEDҔߣq@I֡?o܇&9\ao,ƺ&Z0%+ cyI< 6m^e_TUTFv(Tj(چ Ë/z6,0 dTlsļHBvʗ3`}0Yٳ;|u~.+d3tl7|K \@.S(HcK?pXvbSe:鸁Շo+/tevrt>#$1fP{yeŞҞ';TWu{3z/M-dvZQƵ_5dQ C3X7;vAr(eqG#pr6؃ߍycbpIlDrpKQ}lj1Es8,<zkaϗ͊ϘbEb+B5_4q`dhG2L)uY‰Xpɘ&ZW%"Xyp'9&XX͙&N!IvʗrВZ&+l&ȖUj $Ǩe\NKL3ӈU?8[c"ź@k߱QRD}V Q*Jb8wRנH-+^/'+ʏyHٞU ]xonSXTʥ$K[6'q)J)Ssrp;^3U<p830nk[1al#.gvJ]jH#M;r&W7( C .Iݓ!Ug;W9$ Ǥ >pԆqo% E@)ᷧ?Z.72H9p CP76OAlxbʘEhPN+"j['IaO 69X0l(NqLFvg4daVFDqQi25.cZ:;D)H 9}'x*?$q Oz2VCߠ!I1a乓bT!wsXxV'̊OVQ0[߄+޶&.$EũKᖱHj F}оxY# A{ % ~GG_%kִISvYJ`ŒU ^̔ V(ѪFD)% X\K.66/HuG;v,[2A1Id]V}-˴Gc-SާN:礑K~n%εDs"|'z"Z:K?Wf.ōw>7rfS徏/Ρixu($"?Ȉ]Bٞ# x;ިvx. ̘8,'N\y!u>?S)0 x9Ԋ8G#=F x,x"q9_k;9v&H{ /fsIۘb04,Ip,s`Y{BkߌR`p FM(lyE2kשP.eV| ud69 1vTr_#~c&fb)_a T./S&֣bE,dĉbDRLkٹxf"L5!eWrۍ +N#!.< ),SKmk qnkbDZP=xq 6ʗ0~}¬S~~UC-O\kk_aaJ[:QUs68khHK1!s8J|Ga%V䯢C] I!e K0\r~joQtSafk{;]ҫ\~G#\;/Š߼?ρ\(g?`4jp;\-R3ؗ汲Y|`;K?ͫ0-ZST̘$~?ʗp8@_F@suw8 ZSQ[sǾ؏}=鳅}g0VԜ==*?~zܽЮTIƝT}r{%F%Fgւ QGː~ n5Z^^,G#\{%ؼDG#p8^ߐs8G#p8A+`݃+p8G#p: ЇptЖ zV-AX\2L:N 8{We!**\5p8G#xUl浸0.4FF;툰G~ txjqESݏ#B+G2Ci9ts]ǯe3O;Tpw  '}&s3T5ozFew:v A!ШU=hz$m$ڻͯKG#|𨀵޻iAtqhfWOB> f󻂐sCu] Xdu8ԍWy klలeE5K"=T&Tu '-[Cp}R,jOSw ?7I=ܶєD O5T*`O>엣Ou՘һ y?DʼvitSjX޳rqqX L7cGU^;|vwy̸ mm9;W/w߹ wr4օ-u&ߔE@IDAT8sH9gݷ/+kσ%w/2?|r&fBVya3D!Oʵ_{QLpQ[b77+J|)k3<t2jwv;7~$cR&ݏ7&A+7U<T✁pdC٧[PP&(L3 ێc'Ȝ>Ir{9CeFNR/))Cmp"Ș0?s7|+v.h~p8G@G,j0Z೭jctaSFk yʝHq}%B-xᑈЁbκ( "44쎬b0@ED7v~Kw3[tС#C%jE0t3TAr ޜG,/d SX Ȭт>r ٳaDq?n~cEr% /9˜ O5d <*t^OpSoi1˨: [xb1~#m((wVgƏ2/gW>B0 27G^L)˗g)SX!Mq|nͷ$X {MGLuLߝ@/wNp8G@D@C:YAm._')MAO-8FrI?g6"aZ'n6}LuC 7KTZJݢG°1xP7Flp5C,bVu eeMD ZPVNE4?8*!;HGr,#ly-.@Dh>6*eBJ"˷ehޘVTUPb9V}'4_ 6Vl[u_~JmY49bqCu&a|Y?alFFr4r4t5:QȰ^yqp[^(Xp,y~ލO.B{E{\JO_މ_p8G#6言PG.;eD[/T,%|Vq]梳D y~U  q#0h.Ybu Ws )|?HBK-7X1elbѦvr֌pk9$ƂEccֈ꺋^X5ɨ &AÔ.ûw(#Ԣ`.ϵ˗d>17OU}GGsS[oqXyE{M]b܊v"z@ҢòYs0>I)$ [w76-h[:Z(f␤|dz,Z~"Yh.jgK`cF9d!(*Bq.YD_;'P\*] Js$!"& Iю]伇N%~ 1F}[G :4JVeȄDA"o}h Q^ÖOF䈣U@{=h*+8G#p8 ؅%IS6:֯/Ow[+AlTO*`şhq'y(q};^9_94ޔ>]P.fwpNr!ɹ|Sow6VPS3py{Te3մnnh&’E6Ki-=g-*(ÈiHO589CA7ևŠ(eġ u;OՈoCH7{o3G#p8WntqPskJ_+`*wr~v*9fq eh`'O!瑗ɔ6֥PnLD3m꨻)Z^@-ᖱZG^Ba&Rua,z$x/E  T3EmHSI3=jpr"A; 1~"ǼlC4ո8\+:%'GjrAh4+Ij:78G#tZD_!槿Fm?Ox`#4]GnE[>pԺ, G̾qލ G -hJ90qye2gHEPږH cƜ!1-h #UJy3p|+ض/;%rNvzJ;p?Yx8 ϓĨ`#xe~\'Pu?5qM:[aDap0h%&.+~.>/}[sQ<ڄ4=r" 9T~M)7h»a!q($g.:ހhG@릏 ܷc홝"蜼*q?}'qN{qA蒑gi •~= ʣQ9OԢSG#p[ x7Az`Ҟ0TqtЪ|S =9ߕ/b5G>xCɠ; ƌ`-uGQ9d8=Q5$r6&Q;R_S˒7D; @3M z-<:g{aޚQ]h~8 hH ' w/) -~x = #2q |@uGŕ>&Me-7i/46'5?a(K4/;!b'ij?LkE/F7?G#p8Fd:q} ⠧_c9N~^wXBv)c<\~dr/^w5^gt[kQ^){sqaH,(LgZՖFMˡH;Jc2X< ;j C`2%DZ?~qL1" I 1izܯ=.bJjpqZ۽>?qZ#p8k 5RN^k 8½-kC}yek 8^G#p8^E^g@sM>SpG#p8@#} W%p8G#p8 \f#p8G#p#p8G#f 5S G#p8@_G+`}|G#p85o1Ytm@T#iy Qj1.s'~C< mI#]'9G#p8G#ЛxoB^ġכbv>(4,Z y&~2!\ԴA8v8_˾xU7FتZ,PwsG#p8GrXR  :k{L@e$?X(>ģ1VD܊KìhbDH\ gu[xa7 4n&5~38G#p8G,ny#qcFhI9tM$5TZ)L\3}8%unjG#p8GWh6 7hfOF=B%u݊s|m{K fd9Ѣr_{.9Asj/9C@{5G?;Ky،Pф dB3=u$oAA+3ǫMCBDT* ! wrOA9~ <p8G# <8øoCLh?9RKq %g~&9aڼ1z ͗Dt~-k* Y00,-Px:臧 ?Q 4b!ͣS F{K;iS9|]~qcwb#eXr-[084rY22a -B.}ҪθGËqf;c Å_?#sBB26vr8G#p8}$`,+NHD-Sk좧LPFRo! 8L]F ƩؼK ɜi1HM7cE̔$8@( J/ eLMAL-[S(+kBH ,-u(+4?Y9`ZEdqsPsQ gqE}PG2c-Eu vDpT!].;υp8G#pn߸|$HAؿ*!HkQ5"'â)/E*k{*领ԭ10i46~%%;21CeH +vs !aXFZjb}yoM.+pCЖ_z#>AYL g,;ވOrL/?#p8G#+UR |5"j\D2 ӢPZ,Z-_->++[Qr+1m4S~AQ`6"fcknIą~ꈗ_]`rz5A \;޸5vsyrK ~p8G#!(`)'G~pXtt!ڦO"SPE ~\߿sY"47;O(vNOz7xS<9G#p8dr6\U(>%;⓴BhCX N׊N*LAJ 290)\,ʙ9[(4;~pu)[S~LJƍDIQɘ2~=)G\ϝ2Wa5G#p8G#p!A+>B\~XWVʃ0hQ6⑒rZw LDYO*B{=B/ƿ *I8c(g2>o6*8h~OhM9-͚Dϙˏ%Vp"ڄ}Xq+bNoEC]>.$I8vuۯFgG#p8Պ,b.,+ʊ+^Nn#fCB1~g.\'%'chR"qMaRIWSGN hrd|]dycaIcP& CIŪᄑ^ Epǁ2g*bhkwviofXhG~^z,̫~߶u8'G#p8G d2] 0aqr]ϔv\={хaHr"/XjQ^p 5?M͗; E˅ aH,ALr݈By8Q\p6"䓃~u5?#p8G#}XQ$ UxCZcy8G#p8@ z&iHp8x.G#p8G@WVshɇEU9G#p8G#+\}K r\G#p8G#'.^L9G#p8G@5\S 'p8G#p8]Cֵ~/SEE!: 5fRD ִH\G\ѯkv%i4MѸ6?ͱbt1#;/p8G#Cf'ѿ҇h,-߿tJCna禑?t}9?F;C? 50,?{oVu,,Y$O;v'!!%P(Q]n{оGK^x~\:  $f dhb 6;Q#KgdY:;9ڿs{ٿ=Fim폮By;On'SphuXgwjhkv9Q?"_#G#p8@"*/Ea襣ʾ{voDVV뗗ݲm::_E͍YzDL$07JsK7Aa"ľo' _>< |$7ӌBO;lMxZOTFj5H=9lG#p8Ѥ s;*łŕ$5_.YV0~?Xt:Q7"intNI0d!ZG䧢m2/~ OМjE'^Q̢&Fgۅ,~Ѽ$˓ WhoR0ꓑdAr8G#"0flCrw5aHϡldG}o/De4B>#zؤ$+L5&5_c78G#|S+*&W(hEQu#ծ0HvP>뻔FyQ{8D/Įi ~9ͻk,F6ظU634HbA4j`_V1H1*<"֬ WݾEßmY,ȟ(6+BpSKŊX6&v|-%@c꥘\hz5k $1ul#ax|xZ3 NWBsb7x@VUi^$m܉,^}ލ䏷D߅_@ ˖_#ePJDz߿#'7!}[2\^;[;GfV*s"bJu2#Wu˕p zW鈢>{JN7Ti #,B8PKί_ y %'#DB `$~Q,>yr4qTU0G#p>#!QQ.Th/]\sۯ4Wcʆ>:;1IAzL*/uEՅa#r30V_{{ۈdfKK@3`@%m sR!1#OtdZaÉȲtK06Z0e{R೘0LҦ` P0 nhϴ! K}Wy 2:&n]յhEk>D/ŕE5`ׄ\;Z?rq%,B#:YOplدA磵bL#ۆ4އUARnr4<Lo06|gF!-<:$ =8{3ޭk $= X"_n| % [յwbyYV~HDWuK,ŸݻD1Dw-pZ}=XNu rD#.Cq _$(}o |黏o:ZAZJṫNG~; Y&=6##Rz)KEM"xJ@3AkΠWqODZ27J.2UE6?#yݣy[sѷ H9rFW΂K;Sķ#} aQ2D/D<ȭw!34ۑw*K'g?9)9G#L `I31m4P/.t-F(Ѱ-ŪEkL$`Ae:[9dCL,]L($0 2p| s`,P~l!9X./$t[UX(O4W62{3@{%d s4x֋ʾOMCƋp=$xPbm99BU9%eIb S*v>"N=l$S߱k"-.T!IIbC&o,(ө"?'>TO(RI5kj($Fm}0(}*ڡ\ #>2/4ҷD!;AO[g?  CP݁cZlYb9#GұͰ#Sǚc,%٦zcp8G#0]:#7@&6pu2XɈF'$B,lEtdF >Q5R:[rD=oxWĤ3+U6@@OOV&bh`aꝫ{gG⠄xO+ ljpq1!lTvv;CRR.i:=KDv6E/;^I>ozr%~C>B{1Qϑ=KX6%`jK$%0+8׌*̝5;Pct!@^R^a榏 4x#R#y)U^EGp8G#D*P,."l9֗7?$D) iGN- ҥD'Fc2ah(_r 'Ѻ"&:&G<:m¯*ʡF[3*kX"5v]%-'*k ٳ>D칒A(CYN8w'~~  ~ b~T޻3Y 1-<9D#U3H_Ja_(A{<#oz04Q3 hs Es DN$PfHw#p8@ D%"\X,f}4`șq g:Ez3.[&D*9[$ -BZS&JJarU%%Uϙf9V8:Zi%[E KgCʉu yRȤcEI-lD]z*Ő`5sD3rmejZ\JL(5MmEFs [ Ul=E~䋡/k_k+"Ǿ\߸F7*Em;Rh$ Ԗ % P8)zF'H:/ywuIN9 9]^zp8G#zFI%DîiGHë&2;\_CJ;B˧Hʃ{ePuڰ{iܶpgz0r_h0~i@r.M13K@q!F8!J$35pGp`ٶODd~8wùUWv)g}⳿}f>)zqc;oA'EO#ҞyU0[L;JL8Gⱡ9`%oDSClgQ`pI%}cG܊G#p  Y̐ύzS_4Yn 2m_=qZ]1 %fUڋ)Tsֽ74Vi eL q=HN0#2ᾁDޖz7rF–} M@F|PUs" kw3f:E'FqNYvpŽǙF]fUE% _ob!R ީ44ANck 2Q+1e"ܔeE`#v#(|uA>]Ca_ӾRDf |b3[hLK~rٛ!Ym"wG|w\tһOPAY9G( QPu@VGaDǦ b7$Қ SO.^"@Ή+> (VJ(p*&RN<YoEGPfy ڋ_NV)=K{\ uU93?r8G#$͜938F{>g›sp&L]:#0Չ&̶_#~lxF ^*cC~qL:_7*EP&/2i]bl(+˂M]J RE7aB߇g_\`gCEu9tMoo,.<~ ȋg=uDGBu7i8;^$w*<ʫlIXؙDlFhn ҷDi;ZE坤NJ"G^g BsG}.B=bs8G# vA6ltCdCBCmMR A5 9"GG]z܄ۖ }t*]e$|p(LˑBYd1WבnN:$7m\MtBꁐj 2㾥E31R~+ḣOL1ctG R4`4C -[ԼȋPh~p8G#0L6|MNk9)@+80lrOY0g}ĪԆe7ORw?`ȢԨMKUEz~/?MzFyJG#p8 ^Q||J^R8F{. {Up?<؃n5#K@4_mГFeB7.HG0Q/#MC7ASW'N#p8GB!.4G#p8G#0^z1eqjG#p8G ЏW#p8G#L+hfՏ(V r0Mtoي=#j)L깽 ޚš=&J|MZ~g퉩B@hY[.h/V ?H۴^}D2Xt4Cmxﭽc;5y68Њ~QR&߀w[OB 9] <6`ȪC'$M c0n0>Zi$>杰!/{HY#J~jl7Y;y;On]Lm[YK̓܄`Q2ňr[hZ26t.DmGjz9D\ = ~L{Elb=bAaO`,1ptC[95m QTY",1~9})/6~OQɦ'=W-}sy;}@y {rdG}VAHo|=_Oo7oA==WaO|Ɩ*%ȢẼIH{޷.3{1do3}ˋ`0h?9YWL=A ~erkP Ay/DW k%.lʯlMgIY7Oن\컘SxBYFqLXzm,Jp&=6/`*˕ , ,K c:d|}dǞ1_bE}aLOWw!W9XT=_:E0n#T7>rYI%.S DJ'N ~E;adcM&wJ^Q̗PZy9:i%c}Kb0 x)ZJEŠIUyOn ]īGO;lMxJr{^磷Tl,oRt1l 7)n7Dj&·E.Mo䀃%COVWYE/9ct!l`,tq1"tl9z?~FuhLjIA9hGE>X0&rz]K<ƈe4';TM@LDp%Xg/7> cw*+BGnIߌs3;Fn^K3ѓUeQ~^25*Pqb1<g@s*4 JHZQ27N+4)E7#ŐŦ'Q_3lO;Fڏ.rm (~Eh$ I7!8Ul3R ?DS5\у3?xU1q b㧞?IW+J |%ipi*"m߳${f~?|F0AlG>mC}}) _p386d"z:M#Ȑ/VV1Ҭga1C/ǀN9ag#}w5zVr! G)=KjRt{ږ036I;U[n.j"3!| :ͱgEp&s*6~jS d{ gE{:  ނt[^5|3 0'}H%PW_#5̝ _Z'ܔR=sr~.>Zy8?0,] anԴj|T3J1L(,$8ɵ|Ih/'ix68B&2 U0nﰴuA{qDsKHM)H+ niǠKI=MHv9#Xn72łe#V;9D4)S# N|8\/s~:&]lz@rL^fdX[n_lzeGp}ln?+QJ1o֢FeEXaVkjPFVZbӦ]2W+bٜB$(`;)([5ne'w`[FI}[^ CVl»'X1@ˑF3ʹVŸڥ(ɶ&D<(mqQɟTr|\lu(m@͚e(qn%_Ǚ__]Xω߈;mBg#)lv+J8+Sл}Y"m-d#~5 2ࡖ/y -ԟ܎w yA^#Hf,6[V1*7zAqN9_ü'|rL{ k!0AQu8'`Gک̫aCBe9\ߵemE:XELsvl T"#b JKkk" >|ВAѶe˯U)Lss}Фּwi$ %%t4&~TI2oe(z{'mLϖȨ;PtlGA(R\SJh2k%/x<op4geNvkQ> `߯tc_{>.ISx) (N!/L#iz=FNnBd=Ҝ>_J+z֟ y_]h= n>%3vS?ރtg`/A p֎Odt<"ǻ_^AE"oƬ0Azq*0iRS72FF fT? (XpޫR? 'V;)aGd8/p4IݸVW0)XM#b*<+4Wڡ5jԲ8C3(]z.DΌҹv헦SmH7`0dɄtqZľh5n[,_g{v~ a(֗F2Ѳu+4p,Tހ%V: ,)9ȴbɺH}$Izan- EtD|ҨI5Lk#3p۔5b%R0aN1@R(H6,Y Oim a0.)HTaJRVf\ܱCgἔp+̸gaƌ<ӑYj '-OWæ6)n&1goğh@Dkh] h<ް_NaHvoE3&|dZm \"Ik@X79E-W-=xGi Cނ= vrSOYnDw7ZZOI9jɱ=]B慵{#}%#R-+ׂvr@?-:y2h}yiz x%.$BK0*IV=+Ѹw?3r*YNi0,(ioFC i! ML>phWΟ4ֵ߽AvR:j W` |ћ/`$lUމeiZYyAydd=Nxy#= p߽Ko=krgW?Nqˈk BO;B8 9/OqKE% "/ᬨZ G&(|Q?%m gJ/|C2Ll* >uL} }WD~9 \- XmxgA/8lV? $#ө-hGTuj/=y.m8Yt`‰$Ue'"q o);NahXSE%kӠ֚l|J#8MˮD `4IeBْ@41KBp|X/ _&k9Є?B0hBJrDs~n)I[q;07#|⻑YOYΊ:߻si}s0/Ua|r9^tϼ rS>}O8Xz HAI?$$X ]4,rfy"󒣂\n}Z0wn%lhKO&&?]3M"RҷdjϷy##YT_>T2e!IL]2i Ӂ9 %ARnolj$iV-/h_fEeEvG( : Z/'4&,H Zy?/C;NT{K}~#I#/ ɟ\X큡Ln)IYH<VУ6$Ikm8W`So K.?Ry+h+AEѵd C>9R{BM*ȋۯΕ_CЇi½8s)9o?KmMH8QPa&^=)N}{9isG/ʴSa:Ӽ/ڳ)UnI'G=^v<xu$H-!= AFdyL`ȁOI wn$f%XQ<$lt"+jzh>ej\F[+;g1}pEJLZ;I;.Zklr/vi ƟX n=L?o>I*zH^L8M 'SoimRqۊi+%\onЀ65Ȥ. $ݜhyu$d~'|gq06l&֑&l=,t4i2c"xCPq3t^JDTNt2t.ϗPOrߴ'L_ ^;q Qz0.qpZȤDa#H=47~D&BѻIQ%I+?vo03e*a+eDah@B^j5!rX^g BZewygrՒ]v7²c&GLG} oS^y%+ϊt'.䕯Y%l/>5ԍi D~R$oMWdlGh3ֈфS?:!R/QGk/33d#˃B!'TW,,#۰@?Cx2iOM ۍ+!ϒI[TA UxLMt! _vsXZIxHJieI.P/:kTKU{ _whf.],XK4a!LiPLWb$Mya>h'?<P\/GKٸ3px&@}g }9O%L!ϗ Ӕ6ޗ!A/H,& ISَ[v=K#yZ\3 YϾOH!v2;]RߘxF(.EU0Y ot Rx/,G- *f] #^~zr0إ.&QB73d]L4Do*Ple˱,dެr^`)rBZ{3JHɧbE%CpxBJ /)qqH cRP$)aD@:%7-~ו|%dFXzhJ4p{q>]X\8ET2a%s#:/T}/l"Vq7j3ڪ,O2UsH:p^|Z>D_/4VQB.^bĄn+lrl eCT`C?™{L.úBƜ Soʂx':h \p#mqҋ&Sy32y/ Ei:i-MJZ 60c4T4IJF i@M60KoAjH#'24LNRzu6 3㷏u-0Xzœt,![[GY4C- 熵6\%$H9rQY#Fzp,tHy[^ sP9#WӦr/ l (N6]JPiݡznv60o `[#c\Me;>cc\֘"Ƶq|aak⻂'̈J&ʟXv@JS64,ndG{Z Mh.GQ@sQ)g4k,*bw e*Q#|;Ӵn!g3()kQS&ُ iB?ΦVa)v<[Cdhd&>*Q_ ZԨR(E'0:TFXBf0 YE>l-R4ǃK4Jy굸f1 N8XơyB~G)ldDnу)F([Ht^e*3u7%DHJ`mlAW6 DaeN0!\`MlG> c*e^; e .NJ6Rέ#ԭ2i,M|v$tDJ9Y_Pa(E'0D+w㽓hL v׉Z%ި@}h\E^ӀTFFAN9rdLb`)+VG#9) 7|8Hk||iqλ-%x_>oo?z3z2*lO2/1N#i`ٶ V%{fd4/S-8H[3¢?2[z}MSIQ0":wApV2EۏB΃=+Er.bs"d@RБâƝ[bp*})-C [ҷ춧i[ eяWH,h1@ypFMk }W/tYTxVȩ,ƹ,+$y_Wz$!Tm8{H#)膌p_:4tL"c ?#{S98{uS}<",I.1Z=F BO]ăZz5 R#i0m;(n{06e4Y{Uw䬬p>BȢՍ.z,6Ri 6?!'r zGLYYh@Y;sJqIL2q&ZCGo+Lv޲4̪ׄQ8}`+ފb%|5L0s`_g.!@e`.0TwFΜ)h<]'y9\{rs़Z\xl%.Ρ{U|pB7dТf1'%>S[el mA.lXL6t^E/-킫|~=FU>F>V*]t^9'H 7ԸvAcQ W tXT`άꛑwXi-/q lcC !Q2Y3W)fb6ם&sJZa~(>~_B[覱LKw/ɍAt9v"x\e5pQqƌ4FuHmgo8b\3"B{PðЯ3ڰ1P.ъ/͡2j{@߯I/zަzy҄խЄ]G\ rΨm&d M:Qi 2L9s2mF;Vh禮&Bِ_Wgj&"i` :I#7Rc0hK Պ&e:bCEu9tMoPG]VRϛ Q-E΢'NQ]9TeRՕ!.XL1첲|r]@f y<qoZړDΎ#1 ^ _6ՙ+hnάL8ьBڳ ^OIYiuNKGN5> =w@GtLh+}C8[}8X-NAMhZзE {4#UPۓwh*ڃDP6&~j_wTϤ͠1@]H>G;qYuׅI>M -=x8ʥ??*\|ͩ~ ܸ֣2/$|'L\t8G #4M{א9dF)uKLG#L$ ۑ7{M&Ӆo'$ˮȹO= 򲹊`hp/j(Kudig'Rv@"WԢkuyi}QQ%ގ)Nr݈T|f/z`VgC22Cfڸ _ <>ϧ\3)Un2Pg.V%ŹPU%ps|ao@_tNzEp[s,?|1 ؈$^2Ay_s8G#p&@t,4_۰R'b,ϲ16Vz]pS;tgDK~*ڜqayC ?EOykx'(-πTEfp8G#yC V I" _CMuy`<"kh<y6%7@䔗p8G#9E Vt1_?D<1bZ,S7؎%CAņn_OڶԬY45e֣. M'> c~5j\@yzq7Az^F,]3rayq%3ҿ!VkkM: IqpP|Xf`pEkՅ8ᄲ=Ytk_W #2R~\\ ]t^w;aYF3n:r~z܄mp{-:~dmE[詾T/ȀG$?i/VM(wa06DDe˯_YO a1p=pӞ?* KG#p8}F `6(L oх իiX ['@oYSriŒuIzX-f sqzR sl2ꘋ6a^VS/uBϰi[{+=mFv5XY>:;1IAzL1Ţոma~uk׈?>9BLgaK)h& +1#ODE 'FJlDJg[^FPBZ_A7w53׎`a5mp[My'mң#x-A&kh-zzPbˬ)ۆ4~jd궒$GAiC X3\y6+HH={ZW?p8G##0Z33Y1C ,/7>z2M1յw4T V$"xp|f[Ez>~DFulVH۱* wfkEӎ-ϾF#b=ۛ0HQ M]i:ڙo=k{xQ"rD#.C _|Ln.W EdyiQgdVdK>bѓɫ2лf>+g Ed'i7iqBG#p8F@76<ӈ;]'i Zr}gq0̤Q]/'hQ5u,JԸJ gEa+"ieg(v &2\" 5{&I _{L\\b‚;!h4jQn&"ZJ"gKF v0"K#H@e BRaE)85n%pU} ݢ5hg (tH!\ #p8GhoiS.V۰i SrG_bFzN>H%CNaN8ǜe(PHub&dRrq!E%1a> uKП Y#4L3 h,&yI6ğVT@5~m U(ػff-3'6#ݹn7.B{%pIr8G#LC$hhu KF⼿%\*Q-F.hR>Fmt1Ǝ \?"G!8qS͸l+8ZIW`eұP)VV5+DObqF6"grBʛZt}ENߑ¼rh-B)fiզ)dEؗJᎥᤜB1l*|BsdJ}S|Aو0׵h%ݍlkKPN~p8G#9F (kHŚDGGPT^1<Ngu'']>.)a@ὂ;sbk}q8ÝqdA~xBkh=Z }[h~iYy0s y}>6dCsd  Oʁqˌ;i TQ)HltbyPb$/譺Inq ❫(R:$W7%NKw_!:HtwDˇ*.S)xl貓}G]5fhAaӈg^ i"R#дe $iY=}/ӨOȏG#p8_hXK"-<.\ Qk:C8Kì$y:<}Ml:(3XC9›O 4GPƄCOwCcFaYJ HM@ٖ} M@6]^ ߰hܨ1e"ܲeZ7hp!ʾ&ܮ TFl?)hzoFn;=sкx ڙ@ΉGu:g yH??{aL!ev#L(I"en?ҧ6-5c#uȁbdW4#&stpfty#3(.26p8G#"4s̨cP¶2gιI6icb99ƣ6eJΆ(;cNeHeDs,1c>\sM Y0X&J7~'*ˡoB}C;~6U?oF. -ʽc߀=к|qv$.x)c"fg_'/i]Zd~g#p8GB"W>H#͟q8G#pF ;q"`ԉ& 89gI<G#p8G`"ih>gFNr|usV1^G#p8z zxJG#p8G#0!8!<3G#p8G#DA `Q@QG#p8G`*TiN+FYffq=ӝ?spw͓##p8N8kd~2&`9ۍ6|QF,Z @a&Ysކ l06f( ע7(xal F:q[hZubGL|x"s =F[r U6=bp8G#0-S݀eVɀg.V%Ź aʘU%psMY9' ̜U{$kc `LeO;lMxDI)LsK)LwI9yGj5H=9V.jj]$|ߎ(—2}s8G#B#]'͗$|6Ɵ 0Jd픖I|rmî-;J|𙋰|atpi,_Ou?EOyOPZͩjݑd0xԽ*;y]1<#p8tB VBTNIbL5aWtbP6-pxqDH8<*7:@XR/.8G#\``(HOC abZ,S7؎%CmrN}?k:Pf ֔Zb 2HAVkk Feʁ0^ըr-Įڪ)>ѾNsr&l_f_PQ*nrhwaA֮Yyr?6n 7ӌKEkՅ8!?P?T/ȀCnh-9i/,1rrҷ%u3~dmEytkP` zl5RwAc6y`9l7!cB>0bF>d2uAגYys5³l xFg i W0,v1cد@+1ӚQ18n'ws޻7u[dY䛌ݘ@(`ZRPH\[6ֵٺ5?M/钌4QG$!%a(&lcc,_lY9G%[e#cR 9|o#:߀V|6V'#u7 \Zͦ)y(a0rjK>BeKL7mA`_Dobٸwl[Qy$L.'pr=6pۗΝUϋ]s-lZW:LÒ[{1hf0nh6 ahUyX&=TF$=WX{Boþh5ns;t-ϟ?W{PQRbéBp2NGoJtݹbˬ)ۆ4>Մ:C&nmK+1ӈ eQv`D*0dڐHu:.d(ƃHԥϑBl1cX>^Wlwn9j)gHuJ#[az%iY$, }w7Dk$G-BLFMvzL:%[eM:&C   F 0yW(񦒳UƱOJh>YZy$HC>Pj1KGLz<ա550da 5um)c-ͽPy&kSj}AmPKq`g-W݁hrrMy(_n kPx>\'O:M"$_gTJ݆J3YaE90V.%>U+"ق#-j|IMYnoD("cΐ&B3H]p~#Te [}&u(|G=P M-74.,xw3z )>(c_gTŗ{bKyw?2;o NhБU_ e-(D:F05b8w# D*"Ex r4c"U\V8\TxD$@$@$@h9Yꃧy2$Gw_4k$4f3x)Uz<ܒ0rvqoyZOc׈:ujb䘵I{2qڵ¦İir+)EܨY(qWzÆse`DIGpm kk뗋e~AXyC 'OeRNU5"5᤹HtRӪLW2t)׈RKFAoD@Q5GGxM!uֱ |]{,$>eS6_ĸބ;a9>_A*XaS~rVH|)~Be("P>R%Da*Y{QjPR{щ&* OAU!!Ni<>[֒ !0 w@/f;W Ck#-]a yҨ< #ZGJ wgNq-Cz[>|qe˱Ze#(+(CMاCqfsv<{lJ2 4@GJ!u/ėcSdh.٧_J)2C`65@J@|iAֱi9v冡6L1~2UJ b>f3"J ݯn \ԅėV4 kJ:ۮ3aW&grr,KmYS!ӵϖ-HHHH F . zV,a[EP>'G.%!A>ڿN0ը߉?݊ J?IvCmቬ&s݉ת :ú)N'L狤i[jgdV<#IEz2 ]NޡjsCWSp.q= 'rOIF\Q(L4TkOĒ=>h_nBd S;u,6"w&ܙ[snlsg ?y@D7ɝa98[V}=SQz7ι 7FΊyN%y/ XOGe{'=Rɺ- Ц$PnSw-˃N/sU/ҟHƒD/>fEڣc[7CWJJ>3Zް"~R>Yx^Gvb년b*қ[`n`a)ܞjD_¡谣ܪSH-@ki*hFԅo8'ވ0d.\BMUWC['YJ_f#цHHH.ȧЀuxظq-dSيЕ~{ݼ^ӰL&/}s7v*i3guf㓫$M}dZ\5E9q݁aY!&-`g8|qP=.se8KIoFnaQo8v6).ғWp/tStkbI5[![`l~"/GX>n܀_37(=fZ%6$d8_C$1 ZʂM$֫6 /|:(nҪBÆƝ_Lw=hʷ*D9{DP7PONxR |ow]~8>Sm; 2Z,܊MTPwãHHHf@… cÖS9Jrn1т5MJ}<<*ٷY.ber `{j4Ѣ$p(&q/ZQheIv9yε7c2$8mim[Nj0ڌ}z˥XP:TL!^4rY2 M 7z@IDATf oi$?x!/J-j4×w_;đ{&Z9e< Jn"nP^|rCݑ G={'E°I/$`)Ņ[dr_bm՘k:F5$@$@$@$03 OڮqK5ZLbꢪQWc7l S>L>S8 %OIHHHO،^ԟn.=yG9O@Ʈgd|І*8^cmt{]$d`H_%a?V \7 D%0XI$@$@$@$@$@$0iltl@$@$@$@$@$@$0h&!     i!=`u>$LyoZ8:?-)ۙ`D$@$@$@$@3G`K.A0t_:<"P ^V?ssy@b`C7H/x|F_ło8ޱM`Ft--.'vC-ٌT^' C#J": H`tb 򮘏NR Wñmzg8CͰ~n>!fdK]Yhx—oumr=Bo;z4n*<ކ܃sVӤ I>&mng `haiƪ^ӷA4҇Fv,Gsa>ϡ$z7jo$ɜ^i`j˾[ZeeiދcDFnGMM/ &.6Ե"ȻQS9}WX hi_( !e!ԗxgPtת(޾E_:>#!^P竚279RR$ԋG|C9}g$ǨPBC$Ѥb!aNQBI!./w3WCr EfnyR;}o)A=W-'] #BPz_&$ *l:[ >>40M .4 A^4!)!/='K^ɐR:9-,:YP&%aq#2l5w5 ;]uqt#-@it,w??W y35_J_GnEGF1%C+T}>8~XH|i3AbCv ]׈R_l !%)J~"ڨ}}#||ޅa} "3pb',']j_AˆE0Ex%q߯o "nxL$F7CX!_n+7I;'wʽ+aƙ6/ DN1p{vŽ;?NCdTZimV a/RNކ7kWǜe^%ܼnK3S?S))n˨x J ;#j#4!I?Ra {/{ÔfcA]b=*O]H|i"iY!mt9܆$_%i¦5P:,a&y-OCnI-q(v Dɺ{Ev*!v\ IHHHH L|]5$nlVIfz]]^vmO"< gpcڂ/SNub|%-%vL6} B/í':lX^1LŸ' ?@_ L" h])tn*w)_KR5S TX&{s_v*Z ʤva!ܭB8_ YDN$@$@$@$@$0ES~疢l W8Cd_V1.%ED| ۏ3/bκFӦ<*WG؊nFܰ,1f %i`0LW++Ѫ(UϔԠGy%;"O&i޹%!d?\"vqr>>dS2v 1 LA܍f⤒PEeKP*+QWg܎m5(QpTJ a}\/ra:XzE(++E^ jb7_ +,GYgҙ[\yhz`#Ggr4R2sXkOks+I,Dl-ƅJ]՞א{mo[R+| ~J^  u<V`;&'c]y$`x1dK)ZO^v]cѸ8,й"35r aaKLهtak.E7Y QeV< S M4nUR+>de]p|~ d $,\P1ٖa"r"Zk/V3Paϙ9I\k5h-3v! K^,k3 %KC$Te( >E%y ×*U"6ΝE8X2 M fH0! b*Yxsʆy+\V\ΓH:&\#5+IP&N()GʐӀnF(5> kIHHHH`lq`cw+$@$@$@$@$@$@Ac -M$@$@$@$@$@$`qNHHHHHH`b`3 ąX\0     ČhA$@$@$@$@$@q!@dZX.i %# oQKP~1 ŗcXqkwxirlv>cr4ɲ =~H}j;,'`e'Dy2!WW|>u @}.ʻ_:X$!or2_9ҟVٌ :8pJsqU]p'vsDo@ߚe蛛~v<=g&MIHHHfhjC}6!)83<I_E/W!a~2X} Euf"K_1m\E1\[*Ѫ Յ$ V;z?%EQ$9 4-sPoz`GI"bDAi:24Y$&M2EKSQZ% ly4 WpoUR{H`w^Fo29[ɈбZ΄[ACGx?G,(@}:tKOWx JM|Ŝ% p+M y2d>UśѪ{o,rhдjW`>{57~wT| :xeSM&MIHHH``35׾٦#y]Xh5 H<B*{ "n7q!F;`ZAt sN݁{'9\e52<OWcʏG#C_J1 EVݡ/*_#sՕ8'c\B+Gmrɍj/-}5 \.Z-]wɏ^x~K{sˑ!@'kؖb;{sW"3qكɇk$ZQ ŢY0A xhGCLݶ =-ذyp5Ƕ$#Yd`Q{r.%Y> w9+7+ubﯶԨ5Kwva]820'!JWE171aG0H{-pʿy |CzZ_y72V]\DcHU?z?%;V\d˿/$R?L"$@$@$@$@Q\S`Vuh;;/!y丱#D k;&m(q1Oڽ 6'sfXVEɃQmӀ=QwܲeEo˒0@x@9xOaӚ,`@khI(~1z$ #K}JYx)ZH۳:M/o,Vg 鞭0\&=E{MHQ^zK0 ]V.\LJ|UYKC;AL=HaiOפB&MR|k1_K,$@$@$@$@`x^P+S4\fWPReC/a/c/+=gـV C^#ቒ/Lj&xX](۶t,"'o'߉UJm߇5xK |p7bh_M MkQ2t~|z%+ظÊ؍<ٻG0+IHHH'0_!w@)7~f:^s>/+RY Ee`uW|3:zِ_x[54ᵃg+6̆9ɳV<}@mϏEZa[͛'YK3_4}6JQWS쉲{DK_dh4&|JRl6YB+˯IԤ]2EyW:ԇw-d$cM.oîwcJl8O¹ oqo0Skae_Y(sTa'2KM螷֊>r>2e//0WNl;8oGC V8O ҩPS^Fݼ7,+D$H nq9d<)I:.1/KyZu:\|)֠y˔!r&jQY) ,=nZO2uZ\ K:]ղ7MؔظjXdJr˼p.B|)_|*1J6Q-ǰK =FmV9U4 Y^}/c@d Փ{+Մ>3~)#h;иAiŇ콿%KjDEIxb{RYhX{j~D#[G{7;CG^% 8HHHH'p1(()Al3QEs̑w$.4ESXHEfs9 K`[yȕ1c/p6! K^h-TkCt46,-5#fe3aHPB9y;üRW0y#色{1$Zw|7 QԪlx+PI$_%ޥ OIս E!   G}V2ڗ߄ͫP#Î;# š"?Ǯ ،HHHHHO@?E"W 3hI'XE$@$@$@$@$@E`{z"|l!V$In=F(lL$@$@$@$@$0{ P{ϕ \bjK<&#     YIlVv.HHHHH`&PuI$@$@$@$@$0+ PE  1IHHHHf% Yy۹h     @69& $@6+o;M$@$@$@$@$0(f:$    (fmIHHHHfLP$@$@$@$@$@ج\4 L HHHHH`V&     `3Ac J`s$@$@$@$@$@3Al&sL     YIlVv.HHHHH`&PuI$@$@$@$@$0+ PE  1IHHHHf% Yy۹h     @69& $@6+o;M$@$@$@$@$0(f:$    (fmIHHHHfLP$@$@$@$@$@ج\4 L HHHHH`V&     `3Ac J`s$@$@$@$@$@3Al&sL     YIlVv.HHHHH`&PuI$@$@$@$@$0+ PE  1IHHHHf% Yy۹h     @69& $@6+o;M$@$@$@$@$0(f:$    (fmIHHHHfLP$@$@$@$@$@ج\4 L HHHHH`V&     `3Ac J`s$@$@$@$@$@3Al&sL     YIlVv.HHHHH`&PuI$@$@$@$@$0+ PE  1IHHHHf% Yy۹h     @69& $@6+o;M$@$@$@$@$0(f:$    (fmIHHHHfLP$@$@$@$@$@ج\4 L HHHHH`V&     `3Ac J`s$@$@$@$@$@3Al&sL     YIlVv.HHHHH`&G |+z6|? u#!hizŧ1x#p ZD|`$.B{K|}зؿC}ԓu4c۱uݰezcP: _Q_},C |0v915h\N`w!%VFȍϯt"+z3߿:fwaΈ'Pd2/ WHHHHH`-R(; E̕zԣ VmlsDBLv`節StWDɿXzrw5La*F9-G/V܌֛Wå*qQ,~õB?ԃmVZ3`hƽ翲D (Ĥ l[p^{.6MHHHHfL̖dۏj1AOÐ5F} vzDR0Xz:@w?7SLO\7ދ@]n=3f01_p]oU| :xeX>\J݋9?) xVe|Jݘq[Wj}a?rixhs=F'6VQOx}J z(^ޅ E|!=g E$@$@$@$@h#~%ibA/  0T7"69c/tm7狘S>Ix9PȐ;MrY%e)uHߞe=h5Gk;F zɍi&:\(PėRL6xK(] H&j0gtlSėR\Y绑*s[݃3ZPi=HHHHf54uꓺ4?8X3.1άI*?R$g^ۻ[gE+o+HJLGϷ'I Z$_<"F еuE.G,_ ff\,RP+a__lU\>H ,'0,1I # 1TT|PnE X6tP]nx Bn΀LE^Y9 OIW/{󵹧^+F]|vXuaHncfkR!LthV}LGֵT@6#ڍ5IHHHH`[%/2ߥ~+6e7*2Q1QoA^vX/wkFgh J q!W@uXv+\3>"W S **g&| ] [I@YكA x2-R3M/9 w)!MҦg9[5l0FBIA- $ӈ$*;J ֓;a:W*VPl&iX[ƴ    }`m{X䯣ᆵ\&۽cP=Ss0Qw&x{5A_(|ǣQnS^凖ljj5AJ0ѐdԪ#~<xzu}#,sᡊBc KbNXE$@$@$@$@8)F3hL7dG˵Q0x]=)7/ϗ/ӤZ5\$'1-<ѲP^ެXx(۵bSZR=e>[@sԟ7p՚qygJJ     qXS3<ƾ^=-}Td}j^Jy L9B9NzJ^ =G[L;jrX& ;Q&t[ kEt9A}ч̗wj]j7j"cTdnN7*>G۩D5Uw5kl8nTiIO7 Dl8UNT ;a t DU FmX-yg&=WU9n\3zr"TfTG/ѤFs0$3F̜jg|Q8v7!{/#Dbv'8?s|:zeuhùr + x=gog?mBr! l%p˜O +E8uS/]᭸C't>>]Ʊ-ȒHowCX]4?R$ư}F۰HHHHH`b86hA$@$@$@$@$@'1vc]HHHHHHL|8w      dIHHHH>(>ws'    @@.NHHHHHL|y6l߷<ͣ{    %0eK+zY2&y n7Οv%ي=+=DvT޶h֟LL [1W>f󎘝C^>w(}; Ka=ryJ$@$@$@$@E`L.Yqpy(,p(lKcKy! ӵQs\CVvt6sy XED܌֛WåAr:F0awB{`sѻ|:e.tZ^\_#a&1IHHHf%,G<_YkQNrRptO+(>I?1>u\\S=>8nP֫'H:˫aH0qH:2WށQ< !l( 4z SHqƳ:evHF2[ ụPhbI MgUrzFUX0 X׋S,$@$@$@$@$p D`%U} KnĚy0yx{֋;p)nú6!=< /$Kx^SöNg})i䜥r}Io8v MϸʔYⷎrvX47 FQ'*lݭx2'AV*Po r߶[p\ gF|zENV Mb:ҿ * 'RhF~ G__ S$&ES    -$*!A18X^*l>_dZXi Rꔧ[3%c ԤJ2$ %ĵuzy#Կ޾޿$tz^운ke2U 1 GfTA3qCAסͨt]ՇDE%kUT,&Yi`,gF޷Y~O?T܆ e(]i{;n`}wʴnPŗu;!T$/}҈X+Qmӧjq o2Ǟ :"7F4GJFzRUQ    nX#s}b^ɐRҬy(CN?j=^-{xa(qoyZOc(Ym dڵkS^4en^Fƕm%a\Qşrm :`[E /eZHgp1s6ŎwBfr<Դoxe-5#a{H5Cْa+p!׆-9i}>G@$@$@$@$@ w@# R4bk#-]aP[!:-\-dW2U=s.ܲeȢeAɟ,׸ҧ"|VY?R%J,PsFNhx#-Uq4|'w ˰>8I63՚DHۨ1FvG$   J`ꂒۨw`RJ"h H#ցY*$3ȏ|=n\ ս_;[qAIVG؅8:oO8uaw<ڬ-Mi#-&Z^!-|,NsrIE?Guhd,MiC$@$@$@$@S&jjyG]ww+ƹ( @9+W:TrCIjg]S~ֆK+s?F%q eqЧܷqlnE>ErǍ/.Z*׭߈֖i, <@&C:7hR;d}i+Mκ-XC$@$@$@$@O 9^i)6w'sOm^e}w"T|\Q U F9%h_nBd *MxYlc+6pd<;LmU"ϸ]t2g&upN\w`QEyu-}I2Y.l'<(&~bQoyq{=iXj+?1+y3nCA9hύr|(>^ÿzttɋE?iG Ha<     )HXpxmm9EwlMӨ\;g֚5k6dZg(bxd$֊F b8eI<סhmMز"W^|m[0 Cf+%x \ Xf\Wp Y-D/Ά\KĐw&n“rv x-@,aF}01e.U|5Jzf<:XZQW}/t'dɺ?eOwOf\iׅ9oUП$O,h{3P_kR\Vz7xf_$@$@ ,G<_YkQuK=7.M|m]*X^x(_Q=\w]d9:ۧ XyC]/Ɵ#g L%C4dTxF}:(蟠fqLfn am?ӂ k&bx^-$2ZQ{r.%Y>ꏘKrRT~|9rվNy;VMϸʔb/Aa0:Ua$4 #Ch+e9h=ێ>]~l}KV9Oݑas}ʍ3w̒d2d!$% RAAskc[j_ֶW־b}P-Ah:Y&{Ζ̝$ Qrs~=9O-(Ž'6r"QY0jN3=-ñ޸a?mF om4ƺ!6<6꒕/?TOj'Դ[QzX1tm,E}G_-nR֩ 34B:u)X΢ydz܁3Ы]3V, 0pW?[sή y~T0/X^a?*sI94McR4|>87â3j0=n2 +jX0l|t%9.n\f#27CWj/Bd![}615z A2GC Q^ DzzUzC@7WԽ+s qVЩh/RQLԿtڗ3~dK%2U-gT\֯@?It5ЁwcJ9!>SsFu0AT'ꦼKh}pJåA82}OVa;,Ĩa mL^g;L 0&&H:R Mi8YE˱XRr,2#|Y*jk2~򣑟z4@zHJToյKڋ$-}OJ.$+_~ܧdm)/*DϪvNzFQ=~$*Te,|i<,2B3VXXOT<=#:WrO oNWgL 0&0&#0bER:VDC[;W!V RFNzj=CﺃϽ.+P1l^'^"Z) 7K/nbnedKK3/5 'G4P&Zr[w׌=o7 ep{ `l5i|;~G>]B)p͓XJ &1L uPMư5yzrQ(_BLrgezQ4) &ʐHl")ƷLʗ,<5jo0 "eKXac(h`Sronz3| 1_AЕ[aҡ"ܨ/<<+;6rHБ%ft.*t 7¸@MgRuŧଲ(⤷%Bd=v"~B g8?* bz1L")PlCW݀SRQ8OY~=G6_{ 4C X3?4(mpM\]& 1!H.7Їa1ԳK|Q0h /ƀ܄Bz)0b`L 0 $0'?$eꜜ-io|?JzIDQ M/HI \*@K$h(eע^7f\q Z‹ອ90&8cF*`==Q_sDUY3N7G]ʀA>c2Jsp܄IGTT 6*][˷Vυ= ؉ٗəR:5jN,|H-ѧ?D.J^rO'B%m\=US%熞\m ؑ;u0yV%j&;sS@%ͣ+)9uǣD鮉:QF9톄jҠ WF*gC.6iNԙG"xځ쒵@Je=5gdʟ62=?6B)_+|iݬY#EF6)YF 󣣟}_d{ r$Y]piq56`r]ii" L!/3&3GqI|R)ωCo8UQ:PE(j+ HtUDXԯՈvi`agDCHvs)Mx\oAŢgЍSh_uUeծS rpOeW_˗W'S-g-,i^JDJUR EH3CsB(._&i,*U; !&rCkKdXV^F0RS 9ȐFj:$cb!L 0&p$r~JnB\sP;p QR1xj[K_h=y+ye6+(r^5ku 2 OQifY jIlbȳLj|cDw@®XX;8@q| lB#G]'Crըeʻaj*^9Z62N'DG6)Ks[1tzXᔬdכy@LƓQ^(%{:dǻ޳fgKWa{.2˰s/}%h? 4@c[T _NLZ4S|w@ b^I^T»ʧ3J&вW%LN'47վ=q"GݎvN)i> f|.ZEs򦽃̶>hzz[waC#٥hHM5̞jK. *b/"E}jA(B4-8Mѳx2ֺLUrٲ&`b{+ݳ;puQLs, _j w O-t:٘Q% #>xcNg( Qh-Ui$Rp--uj\> ';>;zr*;e3<<"z:b8C8P%i_⤷Y6-t773,-.$'M{C7 0|lr?](k=I6kvHD wfRN6,C#v;CZ2~Đ! OhIǵy?96_rhRIk6˱Ҩ<:^*Fty'oпkāE2}aҎgl H\CnN2*MZ`넡<ĭ"ְEwbɥ>`ZHԋOc jځ":g^H6ުLrv'ھK*Em+ä aN:46eQJzptz- [ʏp\֒]].M*'LGeL 0&0&ӧsGIn/E‰7j'c:⼉:Cɵ-,'QTw;YO4jNsL%OG;GJjMSdEK>hmjӫ qk'6݈!\]RŎs+G1.Y4N}qZ]@e~H 6rޑxѲ~|iS@?',U}G͚}~מ"w,_h4Pwv$y<[}0lߗvz?!wfR,-\W!`ںu"_3o-:kq9N K@=mIOP;Awz ihΝ dVSΔ9`L 0FE4-ە4 q2i\As}B+J ONtj*wEEM?RRoSdCt=U/9Z.=yk~W+Z uEBDmƋK6a7& mRO91=McmV۷_'kH}f1%&I0D#Zl*V n?p<ҘSn/,]SQ߃h9OPq-)re>`L HEauM ڥz"A/KÉ"ښ0TPǹ0)ZtU10(2ND+h'?&0$S':k`L 04ڏcB}84u eaHj<ݴOG w{j}u8(Up!7L3Mi6‹Ȫߙr9q$&`L @9`cʉ`L 0&`L`5S5F$L 0&`L 0&>Vg)`L 0&`c" ؘq"&`L 0&@8cId @+kZ8xkkE_"zT_^V~%^Z Mit =ZF_ úXx7ޅ95HGǶYW+7ބ۟plF[Wbi06Z1t>&blןCXgkYb+Bcl}mHZgQ"oo__wHMҩoZuxۏ6nQ_~':gca=3x=ֿlZ{<_+֠璹 fdxLe9%Vzhpu1 =Eo`L 0 Jp\qxZ%BOBjV`{5.+q/6l3qp--q vKJOg5c\eYǷbң alI נ3z)VZ;NGh,JX<^RY C_cid9h'eŲr &g܋gʑW2:N3*Q_[MKE;m_y;&"$)jB;O.z g0އc05 M4 rnsbۜͅ cfVW>D 8Q2IkG˧D962)&(~Rl:t۬XZ1'zo>r ;l nf֮Wsgp_;+`P 0&ǘ@3{.x%31}8,FXY6֦Z Yҁ6C "΁%IȪۅG6:l+SM f%\o ^w犅hY R$S6|& \E4C {4UZ瓊Y4t|h|PޫlQoi)!jYʪہIl!e/7\#o*TU q>87搅06Է Cm7EtJʗmkbK'};aϔ1C¯}{a=/ y oBɝbԨSgRrvJ f`PhdvaɦCc;mfÃNLyLIRI4aL 0&>*`rpփp_XVlyTʘ`& xs;KAp_텦b1-# s菒%jyI-SN.Z@ TIyU'=`^Ӹ,DuhD(¬'[:+/vB* $C g󜒘O/qBƩ.J%K\ w8IH Op}%>Yvn1PG9>.դYbN>|AA䆔/It^IDVv=ۅ82' v^ۗ~{L 0&G@,Vv .ŚCQYl*eW,AY3<4mx{bAd垬Ngcq #Q`2g*K/3[z;^=9V:|jA1>= G26LP/; âI kF\ QAt5ہ{(j!hЙܰҐ?Q%WAҺNÙp?8aZ]}Kf нVB 栕&:(c%/^WXۑ(x,z{: Ұ/2륭 dC^Q8,qQ(j8xoL|Rbo@8j&=CVE@hr=5Ŏr}匡j Og1/|i~',€'I%r<L 0&/=ztfT6/ 2on220ȟ ƚazx燏]^כ%X[3_ˆ60/WMI5!siT%ET틛P8њ[ȳ =D^im`K ų2HHUHJ~J]wD(_k큥+Cw?RGڕP>%7چסN |[H.)_D<( $¿^9|A̧5%oT~]O`Wԉ@]hD~:)_Q7Y"##t)G+ap'\N+ /BǺj\4!,(8iw0)ߐk,0F$".".vt*}(&o`L 0&B($YZNH*)E ^b*Ϊ8|?;Rinҙ s(}L[%\_Y)e,ICʢ1:1?^0hC7 yM(|Ed֌r{<[IilQG8k GOgQPK쒖&Em!嫿e)_"hBS\rR iZp,""Cp_|ψB-_P@OQ-3OBZ+Ԍ ~]]BÉol=eddfoixhϨ}vJ"ܤ,֡bpB!fM2>$7L 0&i 㘝E,fz(@Ȳ4j,yWYF)ᗛpZ8 J(տKL܁+i. [8-xY$-9y[}kpմ08 \NGõv"utBi׼KS#=' )o,_:L/v=/ua/q](|/&oz,4t:-,byM jja! h?9d1EH i~MH|j;v+u.AʗA{]~/B2n'"I! g{0KșpʤfɰR^=KX]xTUVO_}9 Ţ}\t>dL 0&2% dԬ6Fs@֩]ߒv{Neщ p!ZhhhN׺O}r+سYV|y<{0w#ϾN"իQY()&o'mL~i{zޫCFYTI6 o{JiQޖ!;:\'1OMVW_RP~ ߙK ŻǻoTz\B[s:t=/*]_͗រIs2#2+/ CKo ˉf:.@屢aW@UR%k&[ 11 3| c.uu#zj܂܆Xۊ.)~)rq>N}Q!y\uL[JN^NY_M%oTVޛn@s0Oe$*Jnhq0N_s*qM96QN/rwT|Z^cwzZ{"H^-ɱU5>ZC)>x>8_~ 'ǝ)/~G/RΑyV>շ/ar!ob(KALzw_KCJc:R11a7w?Kncm\( B4]sC(g? v:F)QY܏Qj6upMlu{xt[X펺o8_âh;Uv}S+K/=kF20 GTw@7 o'"/|AݗCH[KR4}#D`L 0&&>}zkkr`Z &Ի(Avì^WBv&NsL<$7a$*3V@[ړaU/:PdL -FHY4QMg\XϚM,|Y[8넅:̝@&ՕW]Ч.UXV;iK7Z ѽCI'38kx6mq򁬠>B힆3o|\N$PMI/ ӄүX {\t|p &`L (Ư)x;fOХAϿ8;c.'dL 0&`LH2JR)ä0aL 0&`?}^DVquЫ`L 0&`L )_dL 0&`L L)`L 0&`L Ȝ`L 0&`L@``L 0&`L,`,l`L 0&`q`L 0&`L %%М `L 0&`L0nL 0&`L 0DaL 0&`L m 0&`L 0&pv@s6L 0&`L 0V 0&`L 0&VhΆ 0&`L 0& &`L 0&Y" Y0&`L 0&X6`L 0&8KX;K9&`L 0&+``L 0&`g+`g 4g`L 0&``L 0&`L,`,l`L 0&`q`L 0&`L %%М `L 0&`L0nL 0&`L 0D@&3K`j6Iq`L 0&`L @ ̄%<;-QI\< E!]&`L 0& 0{*%ˇc~0ʗ  0&`L 0&O 6C@ﶽ`L 0&`L ɝpd!ihz h&`L 0& ]>nE}pR&`L 0&@rf{5 L i1&`L 0&xP(G&&0L 0&`L 0&0v p̼3XZfG,8w=N`L 0&`blae ###|1$ŧ`L 0&`L ӧGM@}+O?= L 0&`L 0&@B X8MO-N#w`L 0&`c!\CF; 0&`L 0&0{1򅛎vCo`L 0&`c"\acʉ`L 0&`$tCh4c> ^”x 0&`L 0&@r [@kfeL낙3`Ø* 0&`L 0&C |`L 0&`!6ќ 0&`L 0& M`L 0&`g+`g.fL 0&`L D*` Bu)}:6EWط_{%`L 0s@r7jM+_%90n3\Myz؜&g\J=p-? o s8o ;0r ,ɹ4a琹x8 ,]&-.ѿvu=>$}#3BלR "oo_'Hr6u:gcb4x=ֿGǸ|oL]E?#-Hu[MߗAݿ`ݏzҊ!CnkdĘ:}%h&&^MݮB.^@7}sgq1e!Qm|| 0&6!AA:i;Ԟ$V[΀"?,.}'` ƙЉRbGk.]"݃ 6{~KH .jfSԱlEol>xpXY yaA|ͅb?l.[s6XhQJTԕo2/ne'EϯC͊Ίex2 %+#4GNj~Rv;+WakzL^DR@G{XSH6^\V)շqn)ţ{L_t)Ni/յ+*u:]k j˶B*JC4aeB'6T1&x|d0>P s.Gyhr}X\&B㧁J꨾r=tPޫlR>*{8m; ]-+_Yu;0Y_n@7GFP?7RbHsÏa>,D^hke^XlAq97yEP1`jL'_@U}+H!>LTy/Yyr8O?Kv vVͦE;1medJfu'd6򥪨^)6YTW K{<Hp S/#LrC^&mW.HRM{&*Z 3=Gv \TXkR['rQ>H\>`LGT{>ze4!n)C:Y)_F@IDATYWWqzeDnNԬ`^Ӹ8t%¬':+/v$ U8 u\rޡT,F%I:)<oaKо3ss a0K\:{j;H9()_B'hN9 h)`T{" ;2ilkF0QGm<7?WV%UH(z%mG&MUݮ~e ah4u6T5K `L |l$)$*bj=m4r/I:Kp:8t;'Iq>׼ G76t^۳ y.4/Ɵ" <4Iyr^P4G?}SJGb h]4ZqYyh|D>E=jE çCsapTcЉ:|?ZVL40y( s!^gH =j^[%%/p&.ҏS<}B7b:oǼk5 2շd7Ȁ޲J8x*0+2SO)XZ5^oiTߍ& Ŏ8%{ WMJڦRz鴫&ĥ8)ʗ>3jէ\}`L |l $q}F'GC}ׯE?> y%h~SS3)Io VkףASN_Lõ=-y*jp7X11"՗%r SV\]LĕM`#g94h+Nׅ^k0H-*.rTL:Yw`L;J0&d<(7 >On?PGA+ZYGjf4Kp뢣.@Cv۵%4LNXTN9aS 1d3B3І?o%-yeS>ֿr2uh:fF!v{J|c51NA %dUK2i N:Wۮ{AC;~m|4M/ s&8"tolaFÙNZx:*iEINҾ}J=} Eϑa12&|S،}-`L 0;R#>AsZԈ~Hmf Y==L~ doSkOR+z2~l:f_yӞwk(+2 X^Pp(;"䁜#}-]+i䓄L){(}}B'e+j!IAj.r<: a݈P,FGYqJ諽F4ElEZUN[򮸑F'?K}/ ހrt8h[k 镯5hM䐣Wҁ<kH)"d%g jBS)P3ðygyWծ!r1ɑL͡mWm<%tJ:2&{ {5CS"t\~d>KQ&=BZ}+ Y" ^ECeeC.dEX$E+ЂMW泝2N=,6P& Н? /}4г(u:ـ耛~0'bW߂+g"/ 9\]6BI R),A,(8iw0)ߐA*|UVT&g-/D$VIJ\@]h^+N9<&*9%W1<L"[5+?rYx:֝@SBT_͙ \`?Lˉtڕc%:4O*X(ԴX3#>|$| `L 0&0:>WȲ|/wUQ y~ʏ7i~ \Fva>X>4"on-2CʗAaځB7ڤYHss{[HC}+paFtS\\S[|^KsU|T{ϥEy(-MnslDkHEr0 Q?9ip-)\[`0F'> {{(!@2?˶t>pU),,3OguX(N'Б5̶XӸR%AyK6ڹ '/ 1wCעM\q Z-)Q؞pVvTgK#_gL 0&> X Oa/Ts5! Sl`Ɛ%N|(pq_۵4'$:Ε cae..jW|4\:| ^ JW"] *q](y1yc2W+Y1ڸ{zXACJk]]t^azBv/inN2if|\\O PoR_Cg`9\MPþHseoV9Ӆ)hAke2=F>xOt'yNFB\ӇT O+&n׌AZ5\O7F]90 paRڥٵ3BݕpO| sKUgY|2L 0&k/2oAy[}Fm9EȎ!#',> Dm>cb;[~E_B%\)eXRT6]uIgҩZJ<4\|6t='Wkip %IxP>%rxK/tYi2nf;CֈzޣW`E*^/br٬BN4Lax,ˣUUnLgR٢T$|Us'(2_U.diʏ_C޿OP(׌Qe0aQapQtdv 7Uw&1P֕zZZqrVުm yZVA>.EI꫺=-_lTg.sh?_y T#1&8>0õ| \ Ϲ ]]fт硷iLu2k8EuڮgLq_Y\ E%9$н9;)(g XN7¶3jx(cOGl*5G^B6?2tԣ6Q71yj'и K) a[1]ha{brVޟs|3#ɶaeaُ>Or+c:s }KKi;jW|Z^r0 i&E"yy"wyPW_o ˉfz/].\&DˇQ$|Sd{o"(| -(ې` GM&ϗ3؊cngqP #oc93O0\Ymr5Q |6b,-^^( *ܒWmj)ڱ||H>bTwo@Έ#2&8g|0"ZDKE5F@&}P+z6:?di=v'uWu2"dGbxg{<` `\( B4]CG_tEeTb K='m-BZku{x4l|]=dKfмqN8!^B= aY {Q~SvDRbjU/,\x)+6ΰF9Ѵdi=H>bćU/>qzPfat *`L 0ӕ[?rW_/!8 ]}z ߃u ж E举XmEzdd̹֣90k|4䊔wAOJxBp2xK3i>)MOs SϤNeiB3f%͝@fuouVC26[:@|dh8}vfov%PYi %sigڞSWHTxB0 X6-/`jq<&`I' ܛVۇ&@ `L 0SB|S M@6`w!z ٫V `'u5|&`LMr/ j@jSp<&HJ@QIK`L A+9N`L 0&H/4 0&`L 0&@1@$L 0&`L 0*` Bu.}:6A:i|I1aq?RTg5T +ӱr8a eQolPQEKsSGhysx`~~ G~+ҿs%3 H'~^yT\a|w?N8LᦅWB` Kr`&3"[ N=f6Lgs٬Dg? *Z̸ s8oGS _ǷDڽho_>3l0hDFoBd,+ü+]r~]Hb=g<U^E܇MQ| xcAJCt n:f!_c#:\&y'{C{\ hUK٦f{~aٴΗ4=:7cӻ%J;Amp7qzx t0o ?@F~Ϛv? Ϫ?~Zljsl^#W]Fԕ4Ku^ўҪM/ʄx<[.NPJ2r5GS12BIۀHd }'/AV/1iǀہ-SQ18Y / ] m}r/qQf*cRFMP[fGCU$ࡎ6;gYxIE .jfSb{[H{'e_.bWV!ژ,/%]#=KrH/1 #4#Nj~05=&S U=Zaouߔrt(x W9E.Wָm%tOQcQ x|531|͢7Ѳ7~hy⦆kpIC)ȃ\b5 +Dr10t+,+`ሒ,>tF'>TM"EMYAybϤM=(2/Q'=}>=kzmqBh[TI}(n z-F+F@zOWp"8oDU*^ (v92L!`$=}A# gJwbaF@%u{obI$͵E`ߌҗD՝S5b \!+Ћ{JZnu%e|(ȅx+x$YeҰK89Vk3R#ocD INɯd^kߊ҇7Bgz~q}K*|b̗Rsޑ3|积Iʗ򅋕tgՄ_< i _Թ4oy L#}0ño'LAP @6DχkRcC_D^)zy+"ᨛG5+ZV_i/+EV i&`]3.$U6}.)_@2_ =Kʖ%YՇ)aϚy/Yyy(3IU}'ʏ2!eSΣS=U*T\ã?NOr %#e@C.|-(/o".zF`GJzK=0hNDWa#WF2zƦpBa衎S^c>Esԡt~XdR jxO_ =@<ܐ%& upHK|>PZv"g\t]RԱ;W$rIO(czgyit #2aҴ|7 RGB5A )~nZ)luBWhޅ5J2MI(c hEw~ż|7cƪjx`hN>98!t^ lr`pLJ~ih(q *$|IrM&j2jb2sg4K21p$3)AQ DPP']Twuw>Tztթ~(2kCW~^k^ʯWL)O_#*^:Y8uãϿށVvMO{ڗ}b5iJ|)a〭?DR9r+%c嗫EI\ly9?|O״[s7:iNWihW[2 ޝOpآ-.֌`G|*u({(2(_ f=ϣ@uz&l7qմiy M~j77ȽbEO)n w}\vS Bi\p[eҴQkME0QL3o Զdgk1״c4ESv#띍(vZ}/mE?] koD8cؒ:_biJDuG}+s| 뎲?&Oٟݟ9ufc+6sc)GM|P\!Je$hھg. N{Ϡds6=`m>҇ qm7GiJzPfw5u/~"1Wϛ[D׊l0٨@ pӞTM~KgE cOY-:n[~P[R-1Q5mHOXl]Zs]sS_R+ t2p6NȘ NDi`V0A뷑R0"0nn,=ȸvdrN^t0%Rxè$YX:?JvKp 5h]V_+|QM,tVnE%pҚu7'v36>EB`#wz[H+fj(gb0<[,PGu&`uO>W%p\POs+*ǡ޺Eٛ7>xv4PEɱpF{4^<7|@-G/?Rɡ/LǮICDY VfUm> }Gjf5Ё~o :{Kl&PTkSӦM4pɬgxɞ>uֺ.(oy1lJ^XP^1nrCn{ボ6ϣn翄fpK-5%3lgesep:5VI4 =P/~+0ӄRj<T8ؐ Z58yR%M-pUb1ip"}U0PcxߍYiGIftaytRқex/DT&ø8m{�.eyT”VOa?oFLuhe4iA`&ʪIon'X$AQ?]{Lz`:kٟ7m/* դA8B+:sx#yіF]:`|64{A/ w.@_a6\ZI" [ya[BI= BAugk#,[P|uP*CUs=F\PsH `3qadƹ*5Pk(xsоpj~r2sh3Y>tPPm .]FhAZ=sB7ҡJW>C0Դ2ľTg|%-8`ЊѬv945:a.F_BfoA`otVQ+ Әa~J@kKc`j'8S$oZtQpʇ{FycgB_^kyZ7NE*ُ_$q!:\S*>W}h/E7G*_}(ز*H*~xwɗh=D!?6GߩuP$8"!9Q^fY4ʼnYrC>.a!T5bʢ!j٤WV_@t MK5~@4{OpBUUQ _p&|0g:.:r&hAjU'm&rl4TߐETkV;P k׊ݣTa.!n%zn4=OtX(Zv94*_kP٘-XJ1Ao)L#zCKo!S5S Uphpkj7܍Ü<)eK&XrhS|=a7d_LNpkq0zf^DWw"-Z/mʍGF{%fewh*{~z2Ѐ 53(9~V\}#flUIF(ݹB'χ]\:gzVwG$M+lz7O:n_ޒQ@dQtK{KuWgjC'DSx9 ]M/9w>1n2Q2G&V]=;Poѩ"i{7ev^ `ٜ=Cӕ93ax1~Tt-M = &ZMZdGOǓ7YexjC?&ȿ~DwóCp|"SV tEreMZz+y֬}ʼneBJ2ńV~@Lyz-~mZtENTB۫iߏ~m(?}+"n|a,gE2zl^ϳ41VP9_y̛ *k>9Xi??wĚopֳ4"2IVQNW w>G.Ư>uZpЈ)tj9 N?φ=08VF)FI |Z32@֠wD[?Ai t1,| ?^Hʹ.DMՋ /lh!^=ƁIx=/=iI֭u]k{ iA n6;iu\a XQ^W[0]r/QQO𥞺}0 ~AL7n7M375qIŅ2qkY}{WUnXP/'`Ioie:S(hQ—rhgmLƆjb)} qoGA[8nUNb_&3IݼV~&WN3 e%x,1?r)|[̚ѝcVj}~ eg ^Cd!SR$T됚/Y*9}NNҙt -CqK89!s{ÈƃL#[h"؄_ᾦWZS^*S0\D#4{zҙp'A5TQy|κ:m9ț5wgfa;i#( L91mu̢ H-IW3݈ވ7[ᄒsνM߄Yg!{ڠ䴼 G824-Yd3mOFQSG5jc[\,0L{Youmz QvT:ucbPLq6<wo J}J d.J;/Yedi1gYqT0 \c=lLNAaX?2QuT46U;7`#StGM5HI>h.%pgkmnWNs ޟCgɏڏXgj$4t<Zʹ>]8'f}9YYR L- YZ/0rշ)CoYoBW;AO'W~draOkhNf%|aiV9nE[PEp)( w1WMt4!wPЈpz]K[i12\=v!R:xh-B/Ę+(UМizeWSd : r<<̊jx8YfQ ҸɪPS 9}H|jk"dd(9:-{FL>g>G~b5=`Y[Zȏ+gq ڏq+ojT?~ =5Uw<٤UY5 WC1\U;]=K+Q8R:>jiv'UdO 1NIZC5I WѺ? 楗uM*F̽RRPC&W4soJT?>Z#:h2{ESl`3*`:Y ][CmOWԸtiOH36ofPP=ZEyMtxLex`=J蟸~KSP$c"F3p9%j mQSzt^ _ 4~WMiyg|n+Bjt)ΣŞ;h>)PSymD.Ra}6$GcqWӋ`>ss٘é }Ȫ->W2 aN,`oWgO #J#: '=/ „θħ~ _~sbnD>WPŬH*+?\3Mƫ#"\zV$_.DE FPբgc QhNW5V/3b:~Þf]k0㟹1VCӢU,h_zcj[ָl#fŒ]S"W]ja4-"ͫD) q!~.a*HhK+Cuzoo0wC]q6-KezRuPг˨17P!&.mbli/ LCNDv6NMa+ȼLZ^|rg1P^_ =6]JSuwTz8ϯ \mF:fdIAy*U|xHV^72^^ 3+pO)WTvzۊtRshxlbgU E#yU;Y *fyeh-hؐ6_}68%FJik(_Ѽy+GƪSN>C&)uF\m҅÷[l$3^MGuD'H6EtCyvVczR8ܮ>J3,r&X5y~O\@Su>r4KaW^w_7y810|'1c܊9 BtNzQw|y >.z[сsy@Y8o}L7~1JѷG>e3iCi?|t|[vӤ?vmŌbmz2/G Xe" 4+ պwlp k3RZ:lnXՄZ~t6=Y's:JgD~:*CpR-^D1t.'Qhd?,JomtUi4Q+s=YU.zpT~ǝ^m厼|2Sމm#fK rokSq5|2 \=?ghf%Q,u`-螖 `.j =\z'6НN1fgD)Y&zϭDyC#Ԋ_vA~QL͡Y(qõ'JI0Jno4^ Kh[V݈ݿҫV|^\&,Iq{zIT/9[*p0qhY5࿕hѹ$]MmtqŁ^;}:PXOXP9]՟m!Ro/U5hScDӔekws._,Of2qBt̺Y^B=uvu"ԁ ĞŅTA êߢ&v@~(\ԣkL+K输^: %,߁6eg@s/F5@i6<@K3C ,к'ؓ Cӡ$αʥ} R9AK'ᒩ5yaTs;UO]k>DLGIsME1_4 vV_eWP`~l 2x$ m{l4_6E<%6z94&ME]eOŊbJ{lƽ\Ehk?N/G=sb&VA)75iyfTc;_!ޥt Fl?FZv =vӮP/=:=28-}3Ϩ͹ si>yRį~*0mAGѥ[qt,t̽YW pq(:8۶n3Zlb1nϞ4'Mׄ9s>~=/G6/^^CWT+UCϠqS謎qZfؠwMj;Q͹~C@:ȳ~f͢fIV"vxi.̓]Bvgxf*ϊiIX_0LXڿؖ`:9_O>>tBlKx O85t8)}lƄX+u@55u͏thaظN<>a_R-+ Lز.*y(ϒHlNz)c }C&7[4ah +/`ܫupU}2SŅν_鏤1En-ava^^љSg]}؏'N"tqiVb~;(93|G}0i*.ν \4hͱneI5-,Cg҃׌4=IU$ͯ=.rI3c tu< uGns1u־!DKǃcJx'YFL]om9:4iUn!C+CtWQՊ'|rz&2fl{wr6I#y TӶ5 'h c0)G ` `h܂|6ݙё@zIDAT%pcp1l.aj~GW5n%Se _ \ -[U.L\ǭ)*Xgp S 33Nj)߶N/r)9y\.BE,>Ltm1JT]< YBA֏e^bN3c|8RS$(肗{*A3(lh- p#pdщ M%tOO3;w#cR59o;'=c\_9<+KcVB.bgp :EJ    0J,5,F    B@TsA@A@A@{*¼@>wy$   xb]6?\eKЎqitMur!  wii?D57ԋ)47 QA!*   @ X{^5Vh #,WWR    `G ^=) PG_6\RA@A@ b2#)[   H*=,r)   0 `]}̬ʑ-yA@A@A@lf̘1dʯ^)^ 炽z쑃bp[A@A@A@R# w!p0ddeAKH,   q$_y#ϼY"A@A@A@#tXy mz}G()A@A@A@"T;Й 3K   s `>h1痔9/QR    $D A-eV"avA@A@A@p@~e 7D}hk960D    8G A   H,y^y*   "$A@A@A`46$   i X`IRA@A@A@F`AO    "$A@A@A`46$   i  ՑIENDB`golang-github-appleboy-gin-jwt-2.9.1/testdata/000077500000000000000000000000001445400776100212615ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/testdata/invalidprivkey.key000066400000000000000000000000001445400776100250210ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/testdata/invalidpubkey.key000066400000000000000000000000001445400776100246270ustar00rootroot00000000000000golang-github-appleboy-gin-jwt-2.9.1/testdata/jwtRS256.key000066400000000000000000000032131445400776100233000ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAyRUuEd6LczbWCnLy3dUvU7stxcuKcCEetQiAF2T0LLEPL6M1 DBz6hzU7Roeznn7RtJFOAEbcyetVS1VolSliw97wlIWcfJWtx2uv+0PLPAGW0dUQ rrBoWeOExhysB7NWhtChAbeICGNcHoWWrGyqdAvNKLvH6uHfyJqM5cMIrDvA/eoB Gm/31b5sLA+pf+lNwm5Lktss/FhCcHTtZpy/sp5iG2KfKU3EEDQ0FkqCsBKbizkv 5dmDWxke5XpSpBWIlV0Zqz7lhiWvRclG3qek6mxNQqjl3c1DssdvJ7ruV8UORpMp DphFlZKqHk/6a6oafqNJ4BuXOEo9nZ/oOGty2QIDAQABAoIBAFDZETLiFZN3YsvE t911T5gM1DSIx9qZlm0XQ9kkIACwF/kBV9zM8fXW80RCX3fEabB+E6yM0UzmL98g MfJ3N1ylkHlG10pILBzYMWOHOHmh8e/gCNsT1oD9t26oLIrUEmAWFgZIsosc1/b1 o0UkU8xgylYsWg8YTg+sBCaFKkGE9XivjbayIQHZgh3y4FGl41mXzHsbyec28dnV JqK/QtiX1dGXhPb/Uc3Ro3zHEtjwVItNgc7aywajz2N9V9ttCyrSoJli0fUPpRaA Qlk4vftIJCIH4jvaqgyBY/CpdYL8OoGgb3dwZnqxrxhdCa1H/PJ6a2Eze2UmrN7a bZGCB2ECgYEA/AnfVKIiuwfOmRHPGDIRDn1Q6jI2wXBjsFgLw4AmmuXYapc2EAne woHz5MdXdAE9tJxD4nyl0ugxfW3LvpjrAYiXbP0m3CLujzX77vkIJrz+52qHqDLl sNFxUmnF/e4uN62iDCc/gk4G9Hqu62pWp0cAlT+l4UOMMRqnHRsRlS0CgYEAzD5G ztcqAzFH+3IvpAy1+yad5QMPqv9si48fdVTUQDLbFYXe93rzG57jq7eKJ4jkMvgv 8/e9b1eJx6zWW/91xuuHNddtwIYADpyLmdScS2eaDivMZ1ldTjhVT7Bccen9dPqS eX4Nhxx8Uoq8sFDq5Br7O8B5KeSiNFRsMECVN90CgYEAo3iXxOIAmsR+iKOXaf8X Nwmq0KvO/foyfm8s+hmFcJRBoSkAZLiyJgB5u1pb657eceWk1iK4vyng55SuQKoY Sv9YD9XGPaPejT6bcC1PzyhoQJrE8CBLADtoP+bhB0lT6sMQxscyFwca1bk4+PIY 0BhqVWNZ6NiR9ktuNp+W8OUCgYBzYjteXu+9HfosczW21/d3CznoRvJzCBmqPhDn mCTQn+plHlv4M91jnT/Bos7JxuwkX1G34h2C6VFNHLd9AbTny+d24119hjZCCu5S 2Wnyr3S4zMWNHU85AVowytFvCWHG1EgrmqrJya3yc65lbVFFzHhiKTpKEIASUB9O oy2pgQKBgDRIauxpBY2LfK6BVihqwJobIqDkbwHf2e/kxFATaD5aAKufogg0kXGY BgMli9iTK/xD/M+yZATWq12oYmeKsl6YLawhs9XPENmlDaFgLYysDy3vdpbCKQAC 09wD0hUEtLJSIN6JkRAwH9lVi7eB/i1JqdQRIEwFFCtbQKz6jhpH -----END RSA PRIVATE KEY----- golang-github-appleboy-gin-jwt-2.9.1/testdata/jwtRS256.key.pub000066400000000000000000000007031445400776100240660ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRUuEd6LczbWCnLy3dUv U7stxcuKcCEetQiAF2T0LLEPL6M1DBz6hzU7Roeznn7RtJFOAEbcyetVS1VolSli w97wlIWcfJWtx2uv+0PLPAGW0dUQrrBoWeOExhysB7NWhtChAbeICGNcHoWWrGyq dAvNKLvH6uHfyJqM5cMIrDvA/eoBGm/31b5sLA+pf+lNwm5Lktss/FhCcHTtZpy/ sp5iG2KfKU3EEDQ0FkqCsBKbizkv5dmDWxke5XpSpBWIlV0Zqz7lhiWvRclG3qek 6mxNQqjl3c1DssdvJ7ruV8UORpMpDphFlZKqHk/6a6oafqNJ4BuXOEo9nZ/oOGty 2QIDAQAB -----END PUBLIC KEY-----