Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e78e767
Swift docC markdown and SwiftPackage index yaml file for building docs.
codefiesta Jul 2, 2025
4724200
Beefing up the documentation
codefiesta Jul 2, 2025
d6dd248
Testing auto doc generation.
codefiesta Jul 3, 2025
0e3d271
Moving docC stuff into it's own workflow
codefiesta Jul 3, 2025
3e258fd
Trying a different action
codefiesta Jul 3, 2025
99152c4
Trying macOS runner
codefiesta Jul 3, 2025
61d84f4
Trying again
codefiesta Jul 3, 2025
e5cc278
Maybe??
codefiesta Jul 3, 2025
8321724
New approach
codefiesta Jul 3, 2025
2706eb5
WIP
codefiesta Jul 3, 2025
cfeba07
WIP
codefiesta Jul 3, 2025
0c71223
WIP
codefiesta Jul 3, 2025
da7df15
Skipping swift build for now until I get this resolved.
codefiesta Jul 3, 2025
6b1420b
Testing
codefiesta Jul 3, 2025
5e5dd1a
WUP
codefiesta Jul 3, 2025
f195d6b
WIP
codefiesta Jul 3, 2025
a400a00
WIP
codefiesta Jul 3, 2025
62540a3
WIP
codefiesta Jul 3, 2025
a3a4c7d
Blah
codefiesta Jul 3, 2025
48419b4
More Steps
codefiesta Jul 3, 2025
02cb4aa
Breaking down steps
codefiesta Jul 3, 2025
11b38ea
WIP
codefiesta Jul 3, 2025
3c538b7
WIPPITY
codefiesta Jul 3, 2025
c064b8b
Next step
codefiesta Jul 3, 2025
a120e41
New steps
codefiesta Jul 3, 2025
35ed440
Testing 123
codefiesta Jul 3, 2025
c636c6a
🚀
codefiesta Jul 3, 2025
f3813f6
WIP
codefiesta Jul 3, 2025
5967a71
Updating the Swift Package Index external doc link
codefiesta Jul 3, 2025
860e9b2
Updating GettingStarted doc
codefiesta Jul 3, 2025
6257c8d
Doc updates
codefiesta Jul 3, 2025
5e1844c
Applying fixes.
codefiesta Jul 3, 2025
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
54 changes: 54 additions & 0 deletions .github/workflows/docc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Generates and Deploys DocC documentation to Github pages
# See: https://maxxfrazer.medium.com/deploying-docc-with-github-actions-218c5ca6cad5
name: DocC

on:
push:
branches: [ "main" ]

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true

jobs:

# Single deploy job since we're just deploying
docc:

env:
XCODE_VERSION: 'Xcode_16.4'

environment:
# Must be set to this for deploying to GitHub Pages
name: github-pages
url: ${{steps.deployment.outputs.page_url}}

runs-on: macos-15

steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s "/Applications/$XCODE_VERSION.app"
- name: Generate DocC
run: xcodebuild docbuild -scheme OAuthKit -derivedDataPath /tmp/docbuild -destination 'generic/platform=macOS';
- name: Transform DocC
run: |
$(xcrun --find docc) process-archive \
transform-for-static-hosting /tmp/docbuild/Build/Products/Debug/OAuthKit.doccarchive \
--hosting-base-path OAuthKit \
--output-path docs;
- name: Update Index
run: echo "<script>window.location.href += \"/documentation/oauthkit\"</script>" > docs/index.html;
- name: Upload Pages Artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs'
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ on:
jobs:

build:

env:
XCODE_VERSION: 'Xcode_16.4'
runs-on: macos-15
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Package.resolved
3 changes: 3 additions & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version: 1
external_links:
documentation: "https://codefiesta.github.io/OAuthKit/documentation/oauthkit"
2 changes: 1 addition & 1 deletion Sources/OAuthKit/Network/NetworkMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Network
import Observation

/// A type that broadcasts network reachability via Combine event publishing.
/// An `Observable` type that publishes network reachability information.
@MainActor
@Observable
public final class NetworkMonitor {
Expand Down
4 changes: 2 additions & 2 deletions Sources/OAuthKit/OAuth+DeviceCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
extension OAuth {

/// A codable type that holds device code information.
/// See: https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow
/// See: https://www.oauth.com/playground/device-code.html
/// - SeeAlso:
/// [Requesting a Device Code](https://www.oauth.com/playground/device-code.html)
public struct DeviceCode: Codable, Equatable, Sendable {

/// A constant for the oauth grant type.
Expand Down
2 changes: 1 addition & 1 deletion Sources/OAuthKit/OAuth+Provider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension OAuth {
case debug
}

/// Public initializer
/// Public initializer.
/// - Parameters:
/// - id: The provider unique id
/// - icon: The provider icon
Expand Down
39 changes: 38 additions & 1 deletion Sources/OAuthKit/OAuth+State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,44 @@ import Foundation

extension OAuth {

/// Holds the OAuth state that is published to subscribers via the `OAuth.state` property publisher.
/// Holds the OAuth state that is published to subscribers via the ``state`` property.
///
/// A n example of observing the ``state`` property in SwiftUI:
/// ```swift
///struct ContentView: View {
///
/// @Environment(\.oauth)
/// var oauth: OAuth
///
/// var body: some View {
/// VStack {
/// switch oauth.state {
/// case .empty:
/// providerList
/// case .authorizing(let provider, let grantType):
/// Text("Authorizing [\(provider.id)] with [\(grantType.rawValue)]")
/// case .requestingAccessToken(let provider):
/// Text("Requesting Access Token [\(provider.id)]")
/// case .requestingDeviceCode(let provider):
/// Text("Requesting Device Code [\(provider.id)]")
/// case .authorized(let provider, _):
/// Button("Authorized [\(provider.id)]") {
/// oauth.clear()
/// }
/// case .receivedDeviceCode(_, let deviceCode):
/// Text("To login, visit")
/// Text(.init("[\(deviceCode.verificationUri)](\(deviceCode.verificationUri))"))
/// .foregroundStyle(.blue)
/// Text("and enter the following code:")
/// Text(deviceCode.userCode)
/// }
/// }
/// .onChange(of: oauth.state) { oldState, newState in
/// handle(state: newState)
/// }
/// }
/// }
/// ```
public enum State: Equatable, Sendable {

/// The state is empty and no authorizations or tokens have been issued.
Expand Down
4 changes: 2 additions & 2 deletions Sources/OAuthKit/OAuth+Token.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
extension OAuth {

/// A codable type that holds oauth token information.
/// See: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/
/// See: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
/// - SeeAlso:
/// [Access Token Response](https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/)
public struct Token: Codable, Equatable, Sendable {

/// The access token string as issued by the authorization server.
Expand Down
26 changes: 24 additions & 2 deletions Sources/OAuthKit/OAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,30 @@ public enum OAError: Error {
case keychain
}

/// Provides an observable OAuth 2.0 implementation.
/// See: https://datatracker.ietf.org/doc/html/rfc6749
/// Provides an `Observable` OAuth 2.0 implementation that emits ``OAuth/state`` changes when
/// an authorization flow is started by calling ``OAuth/authorize(provider:grantType:)``.
///
/// You can create an observable OAuth object using the ``OAuth/init(_:options:)`` or ``OAuth/init(providers:options:)`` initializers, or
/// you can access a default OAuth object via SwiftUI ``SwiftUICore/EnvironmentValues`` via the following:
///
/// ```swift
/// @Environment(\.oauth)
/// var oauth: OAuth
/// ```
///
/// An OAuth object can also be highly customized when passed a dictionary of ``Option`` values into it's iniitializers.
/// ```swift
/// let options: [OAuth.Option: Any] = [
/// .applicationTag: "com.bundle.Idenfitier",
/// .autoRefresh: true,
/// .requireAuthenticationWithBiometricsOrCompanion: true,
/// .useNonPersistentWebDataStore: true
/// ]
/// let oauth: OAuth = .init(.module, options: options)
/// ```
///
/// - SeeAlso:
/// [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749)
@MainActor
@Observable
public final class OAuth {
Expand Down
17 changes: 17 additions & 0 deletions Sources/OAuthKit/OAuthKit.docc/Extensions/OAuth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ``OAuthKit/OAuth``

## Topics

### Creating an Observable OAuth

- ``init(_:options:)``
- ``init(providers:options:)``

### Starting an authorization flow

- ``authorize(provider:grantType:)``

### Observing an OAuth object

- ``state``
- ``providers``
122 changes: 122 additions & 0 deletions Sources/OAuthKit/OAuthKit.docc/GettingStarted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Getting Started with OAuthKit

Create an ``OAuth`` instance and start an authorization flow.

@Metadata {
@PageKind(article)
}

## Overview

OAuth 2.0 authorization flows are started by calling ``OAuth/authorize(provider:grantType:)`` with an ``OAuth/Provider`` and ``OAuth/GrantType``.

```swift
// Create an observable OAuth object
let oauth: OAuth = .init(.main)

// Start an authorization flow
let grantType: OAuth.GrantType = .pkce(.init())
oauth.authorize(provider: provider, grantType: grantType)
```

## Observing OAuth State

Once an OAuth 2.0 authorization flow has been started in an application, observers of an OAuth object will be notified of next step events in that flow and can react accordingly. For example:

```swift
@main
struct OAuthApp: App {

@Environment(\.oauth)
var oauth: OAuth

/// Build the scene body
var body: some Scene {

WindowGroup {
ContentView()
}

#if canImport(WebKit)
WindowGroup(id: "oauth") {
OAWebView(oauth: oauth)
}
#endif
}
}

struct ContentView: View {

@Environment(\.oauth)
var oauth: OAuth

@Environment(\.openWindow)
var openWindow

@Environment(\.dismissWindow)
private var dismissWindow

var body: some View {
VStack {
switch oauth.state {
case .empty:
providerList
case .authorizing(let provider, let grantType):
Text("Authorizing [\(provider.id)] with [\(grantType.rawValue)]")
case .requestingAccessToken(let provider):
Text("Requesting Access Token [\(provider.id)]")
case .requestingDeviceCode(let provider):
Text("Requesting Device Code [\(provider.id)]")
case .authorized(let provider, _):
Button("Authorized [\(provider.id)]") {
oauth.clear()
}
case .receivedDeviceCode(_, let deviceCode):
Text("To login, visit")
Text(.init("[\(deviceCode.verificationUri)](\(deviceCode.verificationUri))"))
.foregroundStyle(.blue)
Text("and enter the following code:")
Text(deviceCode.userCode)
.padding()
.border(Color.primary)
.font(.title)
}
}
.onChange(of: oauth.state) { _, state in
handle(state: state)
}
}

/// Displays a list of oauth providers.
var providerList: some View {
List(oauth.providers) { provider in
Button(provider.id) {
authorize(provider: provider)
}
}
}

/// Starts the authorization process for the specified provider.
/// - Parameter provider: the provider to begin authorization for
private func authorize(provider: OAuth.Provider) {
let grantType: OAuth.GrantType = .pkce(.init())
oauth.authorize(provider: provider, grantType: grantType)
}

/// Reacts to oauth state changes by opening or closing authorization windows.
/// - Parameter state: the published state change
private func handle(state: OAuth.State) {
#if canImport(WebKit)
switch state {
case .empty, .requestingAccessToken, .requestingDeviceCode:
break
case .authorizing, .receivedDeviceCode:
openWindow(id: "oauth")
case .authorized(_, _):
dismissWindow(id: "oauth")
}
#endif
}
}
```

31 changes: 31 additions & 0 deletions Sources/OAuthKit/OAuthKit.docc/OAuthKit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ``OAuthKit``

A modern and observable framework for implementing OAuth 2.0 authorization flows.

@Metadata {
@PageColor(purple)
}

## Overview

OAuthKit offers a robust, type-safe, and performant OAuth 2.0 implementation using the observer design pattern. This pattern allows applications to observe an ``OAuth`` object and be notified of ``OAuth/State`` changes.

This has the advantage of avoiding direct coupling with an authorization flow, enabling highly flexible and fine-grained control when interacting with an ``OAuth/Provider``. It also allows updates across multiple observers.

### Featured

@Links(visualStyle: detailedGrid) {
- <doc:GettingStarted>
- <doc:SampleCode>
}

## Topics

### Essentials

- <doc:GettingStarted>
- <doc:SampleCode>
- ``OAuth``

### State Observability
- ``OAuth/state``
15 changes: 15 additions & 0 deletions Sources/OAuthKit/OAuthKit.docc/SampleCode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# OAuthKit Sample: Integrating OAuthKit into an App

Integrate OAuthKit into a multiplatform app with support for iOS, macOS, tvOS, visionOS, and watchOS.

@Metadata {
@CallToAction(
purpose: link,
url: "https://github.com/codefiesta/OAuthSample"
)
@PageKind(sampleCode)
}

## Overview

This sample creates _OAuthSample_, an app for demonstrating how to get up and running quickly with OAuthKit.
2 changes: 1 addition & 1 deletion Sources/OAuthKit/Views/OAWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI
import WebKit

/// A UIViewRepresentable / NSViewRepresentable wrapper type that coordinates
/// oauth authorization flows inside a WKWebView.
/// oauth authorization flows inside a `WKWebView`.
@MainActor
public struct OAWebView {

Expand Down
1 change: 1 addition & 0 deletions Sources/OAuthKit/Views/OAWebViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import SwiftUI
import WebKit

/// Coordinates ``OAuth/state-swift.property`` changes inside a ``OAWebView``.
@MainActor
public class OAWebViewCoordinator: NSObject {

Expand Down