Skip to content

Commit 2b44a7f

Browse files
Initial commit
Co-authored-by: Cursor <cursoragent@cursor.com>
0 parents  commit 2b44a7f

133 files changed

Lines changed: 7244 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
mlton:
11+
name: MLton (build + test)
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Install MLton
16+
run: |
17+
sudo apt-get update
18+
sudo apt-get install -y mlton
19+
- name: Run tests
20+
run: make test
21+
22+
polyml:
23+
name: Poly/ML (test)
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- name: Install Poly/ML
28+
run: |
29+
sudo apt-get update
30+
sudo apt-get install -y polyml libpolyml-dev
31+
- name: Run tests
32+
run: make test-poly

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Build output
2+
/bin/
3+
4+
# Poly/ML export artifacts
5+
*.o
6+
*.obj
7+
8+
# smlpkg downloads dependencies here; ours are vendored and committed
9+
/lib/github.com/diku-dk/
10+
11+
# Editor / OS noise
12+
.DS_Store
13+
*~

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 sml-oauth contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# sml-oauth build
2+
MLTON ?= mlton
3+
BIN := bin
4+
LIBDIR := lib/github.com/sjqtentacles/sml-oauth
5+
VENDORS := lib/github.com/sjqtentacles/sml-codec \
6+
lib/github.com/sjqtentacles/sml-uri \
7+
lib/github.com/sjqtentacles/sml-json \
8+
lib/github.com/sjqtentacles/sml-random
9+
TEST_MLB := test/sources.mlb
10+
SRCS := $(shell find $(LIBDIR) $(VENDORS) -name '*.sml' -o -name '*.sig' -o -name '*.mlb') \
11+
$(wildcard test/*.sml) $(TEST_MLB)
12+
13+
.PHONY: all test poly test-poly all-tests clean
14+
15+
all: $(BIN)/test-mlton
16+
17+
$(BIN)/test-mlton: $(SRCS) | $(BIN)
18+
$(MLTON) -output $@ $(TEST_MLB)
19+
20+
test: $(BIN)/test-mlton
21+
$(BIN)/test-mlton
22+
23+
poly: $(BIN)/test-poly
24+
25+
$(BIN)/test-poly: $(SRCS) tools/polybuild | $(BIN)
26+
sh tools/polybuild -o $@ $(TEST_MLB)
27+
28+
test-poly: $(BIN)/test-poly
29+
$(BIN)/test-poly
30+
31+
all-tests: test test-poly
32+
33+
$(BIN):
34+
mkdir -p $(BIN)
35+
36+
clean:
37+
rm -rf $(BIN)

README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# sml-oauth
2+
3+
[![CI](https://github.com/sjqtentacles/sml-oauth/actions/workflows/ci.yml/badge.svg)](https://github.com/sjqtentacles/sml-oauth/actions/workflows/ci.yml)
4+
5+
OAuth 2.0 (RFC 6749) + PKCE (RFC 7636) as a pure sans-IO state machine for
6+
Standard ML.
7+
8+
Part of the `sjqtentacles` monorepo of SML libraries. It builds on
9+
[`sml-codec`](https://github.com/sjqtentacles/sml-codec) (Base64 + SHA-256 for
10+
PKCE), [`sml-uri`](https://github.com/sjqtentacles/sml-uri) (URL building),
11+
[`sml-json`](https://github.com/sjqtentacles/sml-json) (token response
12+
parsing), and [`sml-random`](https://github.com/sjqtentacles/sml-random)
13+
(seeded verifier generation).
14+
15+
## Features
16+
17+
- `OAuthPkce` — generate `{verifier, challenge, method}` from a deterministic
18+
seed. Challenge = `Base64.encodeUrl (Sha256.digest verifier)` per RFC 7636
19+
section 4.2. Verified against the RFC 7636 Appendix B test vector.
20+
- `OAuthUrl` — authorization-endpoint URL builder with all required params
21+
(`response_type=code`, `client_id`, `redirect_uri`, `scope`, `state`,
22+
`code_challenge`, `code_challenge_method`).
23+
- `OAuthToken` — token record parsing from RFC 6749 section 5.1 JSON responses.
24+
- `OAuthClient` — pure FSM: states `Idle | Authorizing | Exchanging |
25+
Authenticated | Refreshing | Failed`. `step` advances on events
26+
(`StartAuth`, `CodeReceived`, `TokenReceived`, `TokenExpired`,
27+
`RefreshReceived`, `Error`).
28+
- `OAuthRequest` — HTTP request records for the authorization-code and
29+
refresh-token grant types (form-encoded POST bodies).
30+
31+
## Status
32+
33+
Working — the PKCE test vector, authorization URL params, FSM transitions,
34+
token JSON parsing, and mismatched-state reset are all covered by the test
35+
suite.
36+
37+
## Portability
38+
39+
Pure Standard ML using only the Basis library (plus the vendored `sml-codec`,
40+
`sml-uri`, `sml-json`, and `sml-random`) — no FFI, no threads, no sockets.
41+
Verified on **MLton** and **Poly/ML**, with identical, deterministic output
42+
across both.
43+
44+
## Building and testing
45+
46+
```sh
47+
make test # build + run the suite under MLton (default)
48+
make test-poly # run the suite under Poly/ML
49+
make all-tests # run under both
50+
make clean
51+
```
52+
53+
## Usage
54+
55+
```sml
56+
(* Generate a PKCE pair from a seed. *)
57+
val rng = Random.fromInt 42
58+
val (pkce, rng') = OAuth.OAuthPkce.generate rng
59+
60+
(* Build the authorization URL. *)
61+
val url = OAuth.OAuthUrl.authorizationUrl
62+
{ endpoint = "https://auth.example.com/authorize",
63+
clientId = "client-123",
64+
redirectUri = "https://app.example.com/callback",
65+
scopes = ["read", "write"],
66+
state = "xyz",
67+
pkce = pkce }
68+
69+
(* Drive the FSM. *)
70+
val session =
71+
{ clientId = "client-123",
72+
redirectUri = "https://app.example.com/callback",
73+
tokenEndpoint = "https://auth.example.com/token",
74+
clientSecret = NONE }
75+
val (state1, _) = OAuth.OAuthClient.step session
76+
OAuth.OAuthClient.Idle
77+
(OAuth.OAuthClient.StartAuth (pkce, "xyz"))
78+
(* ... redirect the user; receive callback with code + state ... *)
79+
val (state2, _) = OAuth.OAuthClient.step session state1
80+
(OAuth.OAuthClient.CodeReceived ("thecode", "xyz"))
81+
(* ... execute the token exchange request; parse JSON ... *)
82+
val (state3, _) = OAuth.OAuthClient.step session state2
83+
(OAuth.OAuthClient.TokenReceived tokenJson)
84+
```
85+
86+
## PKCE test vector
87+
88+
The library is verified against [RFC 7636 Appendix B](https://datatracker.ietf.org/doc/html/rfc7636#appendix-B):
89+
verifier `"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"` produces challenge
90+
`"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"`.
91+
92+
## Dependencies
93+
94+
- [`sml-codec`](https://github.com/sjqtentacles/sml-codec)`Base64.encodeUrl`
95+
and `Sha256.digest` for PKCE.
96+
- [`sml-uri`](https://github.com/sjqtentacles/sml-uri)`Percent.encodeForm`
97+
for query string construction.
98+
- [`sml-json`](https://github.com/sjqtentacles/sml-json) — token response
99+
parsing (transitively vendors `sml-parsec`).
100+
- [`sml-random`](https://github.com/sjqtentacles/sml-random) — SplitMix64-based
101+
seeded verifier generation.
102+
103+
`sml-crypto` is intentionally NOT a dependency — PKCE only needs SHA-256 and
104+
Base64URL, both provided by `sml-codec`.
105+
106+
## Installing with smlpkg
107+
108+
```sh
109+
smlpkg add github.com/sjqtentacles/sml-oauth
110+
smlpkg sync
111+
```
112+
113+
Then reference the library basis from your own `.mlb`:
114+
115+
```
116+
lib/github.com/sjqtentacles/sml-oauth/sml-oauth.mlb
117+
```
118+
119+
## License
120+
121+
MIT. See [LICENSE](LICENSE).
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
mlton:
11+
name: MLton (build + test)
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Install MLton
16+
run: |
17+
sudo apt-get update
18+
sudo apt-get install -y mlton
19+
- name: Run tests
20+
run: make test
21+
22+
polyml:
23+
name: Poly/ML (test)
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- name: Install Poly/ML
28+
run: |
29+
sudo apt-get update
30+
sudo apt-get install -y polyml libpolyml-dev
31+
- name: Run tests
32+
run: make test-poly
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Build output
2+
/bin/
3+
4+
# Poly/ML export artifacts
5+
*.o
6+
*.obj
7+
8+
# smlpkg downloads dependencies here; ours are vendored and committed
9+
/lib/github.com/diku-dk/
10+
11+
# Editor / OS noise
12+
.DS_Store
13+
*~
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 sjqtentacles
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# sml-codec build
2+
#
3+
# make test build + run tests under MLton (default)
4+
# make test-poly build + run tests under Poly/ML
5+
# make all-tests run the suite under both compilers
6+
# make example build + run the demo
7+
# make clean remove build artifacts
8+
9+
MLTON ?= mlton
10+
BIN := bin
11+
LIBDIR := lib/github.com/sjqtentacles/sml-codec
12+
TEST_MLB := test/sources.mlb
13+
SRCS := $(wildcard $(LIBDIR)/*.sml $(LIBDIR)/*.sig) $(wildcard test/*.sml) $(TEST_MLB) $(LIBDIR)/sources.mlb
14+
15+
.PHONY: all test poly test-poly all-tests example clean
16+
17+
all: $(BIN)/test-mlton
18+
19+
$(BIN)/test-mlton: $(SRCS) | $(BIN)
20+
$(MLTON) -output $@ $(TEST_MLB)
21+
22+
test: $(BIN)/test-mlton
23+
$(BIN)/test-mlton
24+
25+
poly: $(BIN)/test-poly
26+
27+
$(BIN)/test-poly: $(SRCS) tools/polybuild | $(BIN)
28+
sh tools/polybuild -o $@ $(TEST_MLB)
29+
30+
test-poly: $(BIN)/test-poly
31+
$(BIN)/test-poly
32+
33+
all-tests: test test-poly
34+
35+
example: $(BIN)/demo
36+
./$(BIN)/demo
37+
38+
$(BIN)/demo: $(SRCS) examples/demo.sml examples/sources.mlb | $(BIN)
39+
$(MLTON) -output $@ examples/sources.mlb
40+
41+
$(BIN):
42+
mkdir -p $(BIN)
43+
44+
clean:
45+
rm -rf $(BIN)

0 commit comments

Comments
 (0)