Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update benchmarking + Support Android + add .swift-format + other refinements #227

Merged
merged 7 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
name: benchmark
on:
workflow_dispatch:
inputs:
sha:
type: string
required: true
description: "The commit SHA to run the benchmarks against."
push:
branches: [main]
pull_request_review:
types: [submitted]
pull_request:
branches: [main]
types: [synchronize]
paths:
- Sources/*.swift
- Benchmarks/
- .github/workflows/benchmark.yml

jobs:
benchmark:
if: github.run_attempt > 1 || github.event.review.state == 'approved' || startsWith(github.event_name, 'pull_request') != true
uses: vapor/ci/.github/workflows/run-benchmark.yml@main
with:
sha: ${{ inputs.sha }}
secrets: inherit
11 changes: 6 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ concurrency:
cancel-in-progress: true
on:
pull_request: { types: [opened, reopened, synchronize, ready_for_review] }
push: { branches: [ main ] }
push: { branches: [main] }

jobs:
jobs:
linux-integration:
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
container: swift:jammy
steps:
container: swift:noble
steps:
- name: Check out JWTKit
uses: actions/checkout@v4
with:
Expand All @@ -34,5 +34,6 @@ jobs:
with_linting: true
with_windows: true
with_musl: true
with_android: true
ios_scheme_name: jwt-kit
secrets: inherit
secrets: inherit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Packages
.build
.index-build
.DS_Store
*.xcodeproj
Package.pins
Expand Down
70 changes: 70 additions & 0 deletions .swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For vscode users

"fileScopedDeclarationPrivacy": {
"accessLevel": "private"
},
"indentation": {
"spaces": 4
},
"indentConditionalCompilationBlocks": true,
"indentSwitchCaseLabels": false,
"lineBreakAroundMultilineExpressionChainComponents": false,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineLength": 140,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
"allowedFunctions": [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether": false,
"respectsExistingLineBreaks": true,
"rules": {
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": false,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"BeginDocumentationCommentWithOneLineSummary": false,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": true,
"NoAssignmentInExpressions": true,
"NoBlockComments": true,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": false,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false
},
"spacesAroundRangeFormationOperators": false,
"tabWidth": 4,
"version": 1
}
1 change: 1 addition & 0 deletions Benchmarks/.gitignore
1 change: 1 addition & 0 deletions Benchmarks/.swiftformat
6 changes: 3 additions & 3 deletions Benchmarks/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.product(name: "Benchmark", package: "package-benchmark"),
.product(name: "JWTKit", package: "jwt-kit"),
],
path: "Benchmarks/Signing",
path: "Signing",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made these the same as multipart-kit

plugins: [
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
]
Expand All @@ -29,7 +29,7 @@ let package = Package(
.product(name: "Benchmark", package: "package-benchmark"),
.product(name: "JWTKit", package: "jwt-kit"),
],
path: "Benchmarks/Verifying",
path: "Verifying",
plugins: [
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
]
Expand All @@ -40,7 +40,7 @@ let package = Package(
.product(name: "Benchmark", package: "package-benchmark"),
.product(name: "JWTKit", package: "jwt-kit"),
],
path: "Benchmarks/TokenLifecycle",
path: "TokenLifecycle",
plugins: [
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
]
Expand Down
199 changes: 101 additions & 98 deletions Tests/JWTKitTests/ClaimTests.swift
Original file line number Diff line number Diff line change
@@ -1,116 +1,119 @@
import JWTKit
import Testing
#if canImport(Testing)
import Testing
import JWTKit

#if !canImport(Darwin)
import FoundationEssentials
#else
import Foundation
#endif
#if !canImport(Darwin)
import FoundationEssentials
#else
import Foundation
#endif

@Suite("Claim Tests")
struct ClaimTests {
@Test("Test Claim with Boolean")
func boolClaim() async throws {
let payload = #"{"trueStr":"true","trueBool":true,"falseStr":"false","falseBool":false}"#
var data = Data(payload.utf8)
let decoded = try! JSONDecoder().decode(BoolPayload.self, from: data)
@Suite("Claim Tests")
struct ClaimTests {
@Test("Test Claim with Boolean")
func boolClaim() async throws {
let payload = #"{"trueStr":"true","trueBool":true,"falseStr":"false","falseBool":false}"#
var data = Data(payload.utf8)
let decoded = try! JSONDecoder().decode(BoolPayload.self, from: data)

#expect(decoded.trueStr.value == true)
#expect(decoded.trueBool.value == true)
#expect(decoded.falseBool.value == false)
#expect(decoded.falseStr.value == false)
#expect(decoded.trueStr.value == true)
#expect(decoded.trueBool.value == true)
#expect(decoded.falseBool.value == false)
#expect(decoded.falseStr.value == false)

data = Data(#"{"bad":"Not boolean"}"#.utf8)
#expect(throws: DecodingError.self) {
try JSONDecoder().decode(BoolPayload.self, from: data)
data = Data(#"{"bad":"Not boolean"}"#.utf8)
#expect(throws: DecodingError.self) {
try JSONDecoder().decode(BoolPayload.self, from: data)
}
}
}

@Test("Test Claim with Locale")
func localeClaim() async throws {
let ptBR = #"{"locale":"pt-BR"}"#
@Test("Test Claim with Locale")
func localeClaim() async throws {
let ptBR = #"{"locale":"pt-BR"}"#

let plainEnglish = try LocalePayload.from(#"{"locale":"en"}"#)
let brazillianPortugese = try LocalePayload.from(ptBR)
let nadizaDialectSlovenia = try LocalePayload.from(#"{"locale":"sl-nedis"}"#)
let germanSwissPost1996 = try LocalePayload.from(#"{"locale":"de-CH-1996"}"#)
let chineseTraditionalTwoPrivate = try LocalePayload.from(
#"{"locale":"zh-Hant-CN-x-private1-private2"}"#)
let plainEnglish = try LocalePayload.from(#"{"locale":"en"}"#)
let brazillianPortugese = try LocalePayload.from(ptBR)
let nadizaDialectSlovenia = try LocalePayload.from(#"{"locale":"sl-nedis"}"#)
let germanSwissPost1996 = try LocalePayload.from(#"{"locale":"de-CH-1996"}"#)
let chineseTraditionalTwoPrivate = try LocalePayload.from(
#"{"locale":"zh-Hant-CN-x-private1-private2"}"#
)

#expect(plainEnglish.locale.value.identifier == "en")
#expect(brazillianPortugese.locale.value.identifier == "pt-BR")
#expect(nadizaDialectSlovenia.locale.value.identifier == "sl-nedis")
#expect(germanSwissPost1996.locale.value.identifier == "de-CH-1996")
#expect(chineseTraditionalTwoPrivate.locale.value.identifier == "zh-Hant-CN-x-private1-private2")
#expect(plainEnglish.locale.value.identifier == "en")
#expect(brazillianPortugese.locale.value.identifier == "pt-BR")
#expect(nadizaDialectSlovenia.locale.value.identifier == "sl-nedis")
#expect(germanSwissPost1996.locale.value.identifier == "de-CH-1996")
#expect(chineseTraditionalTwoPrivate.locale.value.identifier == "zh-Hant-CN-x-private1-private2")

let encoded = try JSONEncoder().encode(brazillianPortugese)
let string = String(bytes: encoded, encoding: .utf8)!
#expect(string == ptBR)
}
let encoded = try JSONEncoder().encode(brazillianPortugese)
let string = String(bytes: encoded, encoding: .utf8)!
#expect(string == ptBR)
}

@Test("Test Claim with Sindle Audience")
func singleAudienceClaim() async throws {
let id = UUID()
let str = "{\"audience\":\"\(id.uuidString)\"}"
let data = Data(str.utf8)
let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data)
@Test("Test Claim with Sindle Audience")
func singleAudienceClaim() async throws {
let id = UUID()
let str = "{\"audience\":\"\(id.uuidString)\"}"
let data = Data(str.utf8)
let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data)

#expect(decoded.audience.value == [id.uuidString])
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id.uuidString)
#expect(decoded.audience.value == [id.uuidString])
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id.uuidString)
}
#expect {
try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString)
} throws: { error in
guard let jwtError = error as? JWTError else { return false }
return jwtError.errorType == .claimVerificationFailure
&& jwtError.failedClaim is AudienceClaim
&& (jwtError.failedClaim as? AudienceClaim)?.value == [id.uuidString]
}
}
#expect {
try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString)
} throws: { error in
guard let jwtError = error as? JWTError else { return false }
return jwtError.errorType == .claimVerificationFailure
&& jwtError.failedClaim is AudienceClaim
&& (jwtError.failedClaim as? AudienceClaim)?.value == [id.uuidString]
}
}

@Test("Test Claim with Multiple Audiences")
func multipleAudienceClaims() async throws {
let id1 = UUID()
let id2 = UUID()
let str = "{\"audience\":[\"\(id1.uuidString)\", \"\(id2.uuidString)\"]}"
let data = Data(str.utf8)
let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data)
@Test("Test Claim with Multiple Audiences")
func multipleAudienceClaims() async throws {
let id1 = UUID()
let id2 = UUID()
let str = "{\"audience\":[\"\(id1.uuidString)\", \"\(id2.uuidString)\"]}"
let data = Data(str.utf8)
let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data)

#expect(decoded.audience.value == [id1.uuidString, id2.uuidString])
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id1.uuidString)
#expect(decoded.audience.value == [id1.uuidString, id2.uuidString])
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id1.uuidString)
}
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id2.uuidString)
}
#expect {
try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString)
} throws: { error in
guard let jwtError = error as? JWTError else { return false }
return jwtError.errorType == .claimVerificationFailure
&& jwtError.failedClaim is AudienceClaim
&& (jwtError.failedClaim as? AudienceClaim)?.value == [
id1.uuidString, id2.uuidString,
]
}
}
#expect(throws: Never.self) {
try decoded.audience.verifyIntendedAudience(includes: id2.uuidString)
}
#expect {
try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString)
} throws: { error in
guard let jwtError = error as? JWTError else { return false }
return jwtError.errorType == .claimVerificationFailure
&& jwtError.failedClaim is AudienceClaim
&& (jwtError.failedClaim as? AudienceClaim)?.value == [
id1.uuidString, id2.uuidString,
]
}
}

@Test("Test Expiration Encoding")
func expirationEncoding() async throws {
let exp = ExpirationClaim(value: Date(timeIntervalSince1970: 2_000_000_000))
let parser = DefaultJWTParser()
let keyCollection = await JWTKeyCollection()
.add(hmac: .init(from: "secret".bytes), digestAlgorithm: .sha256, parser: parser)
let jwt = try await keyCollection.sign(ExpirationPayload(exp: exp))
let parsed = try parser.parse(jwt.bytes, as: ExpirationPayload.self)
let header = parsed.header
@Test("Test Expiration Encoding")
func expirationEncoding() async throws {
let exp = ExpirationClaim(value: Date(timeIntervalSince1970: 2_000_000_000))
let parser = DefaultJWTParser()
let keyCollection = await JWTKeyCollection()
.add(hmac: .init(from: "secret".bytes), digestAlgorithm: .sha256, parser: parser)
let jwt = try await keyCollection.sign(ExpirationPayload(exp: exp))
let parsed = try parser.parse(jwt.bytes, as: ExpirationPayload.self)
let header = parsed.header

let typ = try #require(header.typ)
#expect(typ == "JWT")
let alg = try #require(header.alg)
#expect(alg == "HS256")
#expect(parsed.payload.exp == exp)
_ = try await keyCollection.verify(jwt, as: ExpirationPayload.self)
let typ = try #require(header.typ)
#expect(typ == "JWT")
let alg = try #require(header.alg)
#expect(alg == "HS256")
#expect(parsed.payload.exp == exp)
_ = try await keyCollection.verify(jwt, as: ExpirationPayload.self)
}
}
}
#endif // canImport(Testing)
Loading
Loading