effect-start 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/src/FileHttpRouter.test.ts +4 -4
- package/src/FileHttpRouter.ts +6 -8
- package/src/FileRouter.ts +4 -6
- package/src/FileRouterCodegen.test.ts +2 -2
- package/src/HttpAppExtra.test.ts +84 -0
- package/src/HttpAppExtra.ts +399 -47
- package/src/Route.test.ts +59 -33
- package/src/Route.ts +59 -49
- package/src/RouteRender.ts +6 -4
- package/src/Router.test.ts +416 -0
- package/src/Router.ts +279 -0
- package/src/RouterPattern.test.ts +29 -3
- package/src/RouterPattern.ts +30 -5
- package/src/TestHttpClient.test.ts +29 -0
- package/src/TestHttpClient.ts +122 -73
- package/src/assets.d.ts +39 -0
- package/src/bun/BunHttpServer.test.ts +74 -0
- package/src/bun/BunHttpServer.ts +22 -9
- package/src/bun/BunRoute.test.ts +307 -134
- package/src/bun/BunRoute.ts +240 -139
- package/src/bun/BunRoute_bundles.test.ts +181 -181
- package/src/index.ts +14 -14
- package/src/middlewares/BasicAuthMiddleware.test.ts +74 -0
- package/src/middlewares/BasicAuthMiddleware.ts +36 -0
|
@@ -1,218 +1,218 @@
|
|
|
1
|
-
import * as Bun from "bun"
|
|
2
1
|
import * as t from "bun:test"
|
|
3
2
|
import * as Effect from "effect/Effect"
|
|
4
3
|
import * as Route from "../Route.ts"
|
|
5
|
-
import
|
|
4
|
+
import * as Router from "../Router.ts"
|
|
5
|
+
import * as TestHttpClient from "../TestHttpClient.ts"
|
|
6
|
+
import * as BunHttpServer from "./BunHttpServer.ts"
|
|
6
7
|
import * as BunRoute from "./BunRoute.ts"
|
|
7
8
|
|
|
8
9
|
t.describe("BunRoute proxy with Bun.serve", () => {
|
|
9
10
|
t.test("BunRoute proxy returns same content as direct bundle access", async () => {
|
|
10
|
-
const bunRoute = BunRoute.
|
|
11
|
-
import("../../static/TestPage.html")
|
|
12
|
-
)
|
|
11
|
+
const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
13
12
|
|
|
14
|
-
const router
|
|
15
|
-
routes: [
|
|
16
|
-
{
|
|
17
|
-
path: "/test",
|
|
18
|
-
load: () => Promise.resolve({ default: bunRoute }),
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
}
|
|
13
|
+
const router = Router.mount("/test", bunRoute)
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
await Effect.runPromise(
|
|
16
|
+
Effect
|
|
17
|
+
.gen(function*() {
|
|
18
|
+
const bunServer = yield* BunHttpServer.BunServer
|
|
19
|
+
const routes = yield* BunRoute.routesFromRouter(router)
|
|
20
|
+
bunServer.addRoutes(routes)
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const internalPath = Object.keys(routes).find((k) =>
|
|
23
|
+
k.includes(".BunRoute-")
|
|
24
|
+
|
|
25
|
+
)
|
|
26
|
+
t.expect(internalPath).toBeDefined()
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const proxyHandler = routes["/test"]
|
|
29
|
+
t.expect(typeof proxyHandler).toBe("function")
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const internalBundle = routes[internalPath!]
|
|
32
|
+
t.expect(internalBundle).toHaveProperty("index")
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const baseUrl =
|
|
35
|
+
`http://${bunServer.server.hostname}:${bunServer.server.port}`
|
|
36
|
+
const client = TestHttpClient.make<never, never>(
|
|
37
|
+
(req) => fetch(req),
|
|
38
|
+
{
|
|
39
|
+
baseUrl,
|
|
40
|
+
},
|
|
41
|
+
)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`http://localhost:${server.port}${internalPath}`,
|
|
45
|
-
)
|
|
46
|
-
const proxyResponse = await fetch(`http://localhost:${server.port}/test`)
|
|
43
|
+
const directResponse = yield* client.get(internalPath!)
|
|
44
|
+
const proxyResponse = yield* client.get("/test")
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
t.expect(proxyResponse.status).toBe(directResponse.status)
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const directText = yield* directResponse.text
|
|
49
|
+
const proxyText = yield* proxyResponse.text
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
t.expect(proxyText).toBe(directText)
|
|
52
|
+
t.expect(proxyText).toContain("Test Page Content")
|
|
53
|
+
})
|
|
54
|
+
.pipe(
|
|
55
|
+
Effect.scoped,
|
|
56
|
+
Effect.provide(BunHttpServer.layer({ port: 0 })),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
58
59
|
})
|
|
59
60
|
|
|
60
61
|
t.test("multiple BunRoutes each get unique internal paths", async () => {
|
|
61
|
-
const bunRoute1 = BunRoute.
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
const bunRoute2 = BunRoute.loadBundle(() =>
|
|
62
|
+
const bunRoute1 = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
63
|
+
const bunRoute2 = BunRoute.html(() =>
|
|
65
64
|
import("../../static/AnotherPage.html")
|
|
66
65
|
)
|
|
67
66
|
|
|
68
|
-
const router
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
const router = Router
|
|
68
|
+
.mount("/page1", bunRoute1)
|
|
69
|
+
.mount("/page2", bunRoute2)
|
|
70
|
+
|
|
71
|
+
await Effect.runPromise(
|
|
72
|
+
Effect
|
|
73
|
+
.gen(function*() {
|
|
74
|
+
const bunServer = yield* BunHttpServer.BunServer
|
|
75
|
+
const routes = yield* BunRoute.routesFromRouter(router)
|
|
76
|
+
bunServer.addRoutes(routes)
|
|
77
|
+
|
|
78
|
+
const internalPaths = Object.keys(routes).filter((k) =>
|
|
79
|
+
k.includes(".BunRoute-")
|
|
80
|
+
)
|
|
81
|
+
t.expect(internalPaths).toHaveLength(2)
|
|
82
|
+
|
|
83
|
+
const nonces = internalPaths.map((p) => {
|
|
84
|
+
const match = p.match(/\.BunRoute-([a-z0-9]+)/)
|
|
85
|
+
return match?.[1]
|
|
86
|
+
})
|
|
87
|
+
t.expect(nonces[0]).not.toBe(nonces[1])
|
|
88
|
+
|
|
89
|
+
const baseUrl =
|
|
90
|
+
`http://${bunServer.server.hostname}:${bunServer.server.port}`
|
|
91
|
+
const client = TestHttpClient.make<never, never>(
|
|
92
|
+
(req) => fetch(req),
|
|
93
|
+
{
|
|
94
|
+
baseUrl,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const response1 = yield* client.get("/page1")
|
|
99
|
+
const response2 = yield* client.get("/page2")
|
|
100
|
+
|
|
101
|
+
const text1 = yield* response1.text
|
|
102
|
+
const text2 = yield* response2.text
|
|
103
|
+
|
|
104
|
+
t.expect(text1).toContain("Test Page Content")
|
|
105
|
+
t.expect(text2).toContain("Another Page Content")
|
|
106
|
+
})
|
|
107
|
+
.pipe(
|
|
108
|
+
Effect.scoped,
|
|
109
|
+
Effect.provide(BunHttpServer.layer({ port: 0 })),
|
|
110
|
+
),
|
|
85
111
|
)
|
|
86
|
-
t.expect(internalPaths).toHaveLength(2)
|
|
87
|
-
|
|
88
|
-
const nonces = internalPaths.map((p) => {
|
|
89
|
-
const match = p.match(/~BunRoute-([a-z0-9]+)$/)
|
|
90
|
-
return match?.[1]
|
|
91
|
-
})
|
|
92
|
-
t.expect(nonces[0]).toBe(nonces[1])
|
|
93
|
-
|
|
94
|
-
const server = Bun.serve({
|
|
95
|
-
port: 0,
|
|
96
|
-
routes,
|
|
97
|
-
fetch: () => new Response("Not found", { status: 404 }),
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const response1 = await fetch(`http://localhost:${server.port}/page1`)
|
|
102
|
-
const response2 = await fetch(`http://localhost:${server.port}/page2`)
|
|
103
|
-
|
|
104
|
-
const text1 = await response1.text()
|
|
105
|
-
const text2 = await response2.text()
|
|
106
|
-
|
|
107
|
-
t.expect(text1).toContain("Test Page Content")
|
|
108
|
-
t.expect(text2).toContain("Another Page Content")
|
|
109
|
-
} finally {
|
|
110
|
-
server.stop()
|
|
111
|
-
}
|
|
112
112
|
})
|
|
113
113
|
|
|
114
114
|
t.test("proxy preserves request headers", async () => {
|
|
115
|
-
const bunRoute = BunRoute.
|
|
116
|
-
|
|
115
|
+
const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
116
|
+
|
|
117
|
+
const router = Router.mount("/headers-test", bunRoute)
|
|
118
|
+
|
|
119
|
+
await Effect.runPromise(
|
|
120
|
+
Effect
|
|
121
|
+
.gen(function*() {
|
|
122
|
+
const bunServer = yield* BunHttpServer.BunServer
|
|
123
|
+
const routes = yield* BunRoute.routesFromRouter(router)
|
|
124
|
+
bunServer.addRoutes(routes)
|
|
125
|
+
|
|
126
|
+
const baseUrl =
|
|
127
|
+
`http://${bunServer.server.hostname}:${bunServer.server.port}`
|
|
128
|
+
const client = TestHttpClient.make<never, never>(
|
|
129
|
+
(req) => fetch(req),
|
|
130
|
+
{
|
|
131
|
+
baseUrl,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const response = yield* client.get("/headers-test", {
|
|
136
|
+
headers: {
|
|
137
|
+
"Accept": "text/html",
|
|
138
|
+
"X-Custom-Header": "test-value",
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
t.expect(response.status).toBe(200)
|
|
143
|
+
const text = yield* response.text
|
|
144
|
+
t.expect(text).toContain("Test Page Content")
|
|
145
|
+
})
|
|
146
|
+
.pipe(
|
|
147
|
+
Effect.scoped,
|
|
148
|
+
Effect.provide(BunHttpServer.layer({ port: 0 })),
|
|
149
|
+
),
|
|
117
150
|
)
|
|
118
|
-
|
|
119
|
-
const router: Router.RouterContext = {
|
|
120
|
-
routes: [
|
|
121
|
-
{
|
|
122
|
-
path: "/headers-test",
|
|
123
|
-
load: () => Promise.resolve({ default: bunRoute }),
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const routes = await Effect.runPromise(BunRoute.routesFromRouter(router))
|
|
129
|
-
|
|
130
|
-
const server = Bun.serve({
|
|
131
|
-
port: 0,
|
|
132
|
-
routes,
|
|
133
|
-
fetch: () => new Response("Not found", { status: 404 }),
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const response = await fetch(
|
|
138
|
-
`http://localhost:${server.port}/headers-test`,
|
|
139
|
-
{
|
|
140
|
-
headers: {
|
|
141
|
-
"Accept": "text/html",
|
|
142
|
-
"X-Custom-Header": "test-value",
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
t.expect(response.status).toBe(200)
|
|
148
|
-
t.expect(await response.text()).toContain("Test Page Content")
|
|
149
|
-
} finally {
|
|
150
|
-
server.stop()
|
|
151
|
-
}
|
|
152
151
|
})
|
|
153
152
|
|
|
154
153
|
t.test("mixed BunRoute and regular routes work together", async () => {
|
|
155
|
-
const bunRoute = BunRoute.
|
|
156
|
-
|
|
154
|
+
const bunRoute = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
155
|
+
|
|
156
|
+
const router = Router
|
|
157
|
+
.mount("/html", bunRoute)
|
|
158
|
+
.mount("/api", Route.text("Hello from text route"))
|
|
159
|
+
|
|
160
|
+
await Effect.runPromise(
|
|
161
|
+
Effect
|
|
162
|
+
.gen(function*() {
|
|
163
|
+
const bunServer = yield* BunHttpServer.BunServer
|
|
164
|
+
const routes = yield* BunRoute.routesFromRouter(router)
|
|
165
|
+
bunServer.addRoutes(routes)
|
|
166
|
+
|
|
167
|
+
const baseUrl =
|
|
168
|
+
`http://${bunServer.server.hostname}:${bunServer.server.port}`
|
|
169
|
+
const client = TestHttpClient.make<never, never>(
|
|
170
|
+
(req) => fetch(req),
|
|
171
|
+
{
|
|
172
|
+
baseUrl,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const htmlResponse = yield* client.get("/html")
|
|
177
|
+
const apiResponse = yield* client.get("/api")
|
|
178
|
+
|
|
179
|
+
const htmlText = yield* htmlResponse.text
|
|
180
|
+
const apiText = yield* apiResponse.text
|
|
181
|
+
|
|
182
|
+
t.expect(htmlText).toContain("Test Page Content")
|
|
183
|
+
t.expect(apiText).toBe("Hello from text route")
|
|
184
|
+
})
|
|
185
|
+
.pipe(
|
|
186
|
+
Effect.scoped,
|
|
187
|
+
Effect.provide(BunHttpServer.layer({ port: 0 })),
|
|
188
|
+
),
|
|
157
189
|
)
|
|
158
|
-
const textRoute = Route.text(Effect.succeed("Hello from text route"))
|
|
159
|
-
|
|
160
|
-
const router: Router.RouterContext = {
|
|
161
|
-
routes: [
|
|
162
|
-
{
|
|
163
|
-
path: "/html",
|
|
164
|
-
load: () => Promise.resolve({ default: bunRoute }),
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
path: "/api",
|
|
168
|
-
load: () => Promise.resolve({ default: textRoute }),
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const routes = await Effect.runPromise(BunRoute.routesFromRouter(router))
|
|
174
|
-
|
|
175
|
-
const server = Bun.serve({
|
|
176
|
-
port: 0,
|
|
177
|
-
routes,
|
|
178
|
-
fetch: () => new Response("Not found", { status: 404 }),
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const htmlResponse = await fetch(`http://localhost:${server.port}/html`)
|
|
183
|
-
const apiResponse = await fetch(`http://localhost:${server.port}/api`)
|
|
184
|
-
|
|
185
|
-
t.expect(await htmlResponse.text()).toContain("Test Page Content")
|
|
186
|
-
t.expect(await apiResponse.text()).toBe("Hello from text route")
|
|
187
|
-
} finally {
|
|
188
|
-
server.stop()
|
|
189
|
-
}
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
-
t.test("nonce is different across separate
|
|
193
|
-
const
|
|
194
|
-
|
|
192
|
+
t.test("nonce is different across separate BunRoute instances", async () => {
|
|
193
|
+
const bunRoute1 = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
194
|
+
const bunRoute2 = BunRoute.html(() => import("../../static/TestPage.html"))
|
|
195
|
+
|
|
196
|
+
const router = Router
|
|
197
|
+
.mount("/test1", bunRoute1)
|
|
198
|
+
.mount("/test2", bunRoute2)
|
|
199
|
+
|
|
200
|
+
await Effect.runPromise(
|
|
201
|
+
Effect
|
|
202
|
+
.gen(function*() {
|
|
203
|
+
const routes = yield* BunRoute.routesFromRouter(router)
|
|
204
|
+
|
|
205
|
+
const internalPaths = Object.keys(routes).filter((k) =>
|
|
206
|
+
k.includes(".BunRoute-")
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
t.expect(internalPaths).toHaveLength(2)
|
|
210
|
+
t.expect(internalPaths[0]).not.toBe(internalPaths[1])
|
|
211
|
+
})
|
|
212
|
+
.pipe(
|
|
213
|
+
Effect.scoped,
|
|
214
|
+
Effect.provide(BunHttpServer.layer({ port: 0 })),
|
|
215
|
+
),
|
|
195
216
|
)
|
|
196
|
-
|
|
197
|
-
const router: Router.RouterContext = {
|
|
198
|
-
routes: [
|
|
199
|
-
{
|
|
200
|
-
path: "/test",
|
|
201
|
-
load: () => Promise.resolve({ default: bunRoute }),
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const routes1 = await Effect.runPromise(BunRoute.routesFromRouter(router))
|
|
207
|
-
const routes2 = await Effect.runPromise(BunRoute.routesFromRouter(router))
|
|
208
|
-
|
|
209
|
-
const internalPath1 = Object.keys(routes1).find((k) =>
|
|
210
|
-
k.includes("~BunRoute-")
|
|
211
|
-
)
|
|
212
|
-
const internalPath2 = Object.keys(routes2).find((k) =>
|
|
213
|
-
k.includes("~BunRoute-")
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
t.expect(internalPath1).not.toBe(internalPath2)
|
|
217
217
|
})
|
|
218
218
|
})
|
package/src/index.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
export * as Bundle from "./Bundle"
|
|
2
|
-
export * as BundleHttp from "./BundleHttp"
|
|
1
|
+
export * as Bundle from "./Bundle.ts"
|
|
2
|
+
export * as BundleHttp from "./BundleHttp.ts"
|
|
3
3
|
|
|
4
|
-
export * as StartHttp from "./StartHttp"
|
|
4
|
+
export * as StartHttp from "./StartHttp.ts"
|
|
5
5
|
|
|
6
|
-
export * as HttpAppExtra from "./HttpAppExtra"
|
|
6
|
+
export * as HttpAppExtra from "./HttpAppExtra.ts"
|
|
7
7
|
|
|
8
|
-
export * as PublicDirectory from "./PublicDirectory"
|
|
8
|
+
export * as PublicDirectory from "./PublicDirectory.ts"
|
|
9
9
|
|
|
10
|
-
export * as TestHttpClient from "./TestHttpClient"
|
|
10
|
+
export * as TestHttpClient from "./TestHttpClient.ts"
|
|
11
11
|
export {
|
|
12
12
|
effectFn,
|
|
13
|
-
} from "./testing"
|
|
13
|
+
} from "./testing.ts"
|
|
14
14
|
|
|
15
|
-
export * as FileHttpRouter from "./FileHttpRouter"
|
|
16
|
-
export * as FileRouter from "./FileRouter"
|
|
15
|
+
export * as FileHttpRouter from "./FileHttpRouter.ts"
|
|
16
|
+
export * as FileRouter from "./FileRouter.ts"
|
|
17
17
|
|
|
18
|
-
export * as Route from "./Route"
|
|
19
|
-
export * as Router from "./Router"
|
|
18
|
+
export * as Route from "./Route.ts"
|
|
19
|
+
export * as Router from "./Router.ts"
|
|
20
20
|
|
|
21
|
-
export * as Start from "./Start"
|
|
21
|
+
export * as Start from "./Start.ts"
|
|
22
22
|
|
|
23
|
-
export * as Hyper from "./Hyper"
|
|
24
|
-
export * as HyperHtml from "./HyperHtml"
|
|
23
|
+
export * as Hyper from "./Hyper.ts"
|
|
24
|
+
export * as HyperHtml from "./HyperHtml.ts"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
2
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
3
|
+
import {
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "bun:test"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as BasicAuthMiddleware from "./BasicAuthMiddleware.js"
|
|
10
|
+
|
|
11
|
+
const mockApp = Effect.succeed(
|
|
12
|
+
HttpServerResponse.text("OK", { status: 200 }),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const config: BasicAuthMiddleware.BasicAuthConfig = {
|
|
16
|
+
username: "admin",
|
|
17
|
+
password: "secret",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const runWithAuth = (authHeader: string | undefined) => {
|
|
21
|
+
const middleware = BasicAuthMiddleware.make(config)
|
|
22
|
+
const wrappedApp = middleware(mockApp)
|
|
23
|
+
|
|
24
|
+
const headers: Record<string, string> = {}
|
|
25
|
+
if (authHeader !== undefined) {
|
|
26
|
+
headers.authorization = authHeader
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const mockRequest = HttpServerRequest.fromWeb(
|
|
30
|
+
new Request("http://localhost/test", { headers }),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return wrappedApp.pipe(
|
|
34
|
+
Effect.provideService(HttpServerRequest.HttpServerRequest, mockRequest),
|
|
35
|
+
Effect.runPromise,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("BasicAuthMiddleware", () => {
|
|
40
|
+
it("returns 401 when no authorization header is present", async () => {
|
|
41
|
+
const response = await runWithAuth(undefined)
|
|
42
|
+
expect(response.status).toBe(401)
|
|
43
|
+
expect(response.headers["www-authenticate"]).toBe("Basic")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("returns 401 when authorization header does not start with Basic", async () => {
|
|
47
|
+
const response = await runWithAuth("Bearer token")
|
|
48
|
+
expect(response.status).toBe(401)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("returns 401 when credentials are invalid", async () => {
|
|
52
|
+
const invalidCredentials = btoa("wrong:credentials")
|
|
53
|
+
const response = await runWithAuth(`Basic ${invalidCredentials}`)
|
|
54
|
+
expect(response.status).toBe(401)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("returns 401 when username is wrong", async () => {
|
|
58
|
+
const invalidCredentials = btoa("wronguser:secret")
|
|
59
|
+
const response = await runWithAuth(`Basic ${invalidCredentials}`)
|
|
60
|
+
expect(response.status).toBe(401)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("returns 401 when password is wrong", async () => {
|
|
64
|
+
const invalidCredentials = btoa("admin:wrongpassword")
|
|
65
|
+
const response = await runWithAuth(`Basic ${invalidCredentials}`)
|
|
66
|
+
expect(response.status).toBe(401)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("passes through to app when credentials are valid", async () => {
|
|
70
|
+
const validCredentials = btoa("admin:secret")
|
|
71
|
+
const response = await runWithAuth(`Basic ${validCredentials}`)
|
|
72
|
+
expect(response.status).toBe(200)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
2
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
3
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
|
|
6
|
+
export interface BasicAuthConfig {
|
|
7
|
+
readonly username: string
|
|
8
|
+
readonly password: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const unauthorizedResponse = HttpServerResponse.empty({
|
|
12
|
+
status: 401,
|
|
13
|
+
headers: { "WWW-Authenticate": "Basic" },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const make = (config: BasicAuthConfig) =>
|
|
17
|
+
HttpMiddleware.make((app) =>
|
|
18
|
+
Effect.gen(function*() {
|
|
19
|
+
const request = yield* HttpServerRequest.HttpServerRequest
|
|
20
|
+
const authHeader = request.headers.authorization
|
|
21
|
+
|
|
22
|
+
if (!authHeader || !authHeader.startsWith("Basic ")) {
|
|
23
|
+
return unauthorizedResponse
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const base64Credentials = authHeader.slice(6)
|
|
27
|
+
const credentials = atob(base64Credentials)
|
|
28
|
+
const [username, password] = credentials.split(":")
|
|
29
|
+
|
|
30
|
+
if (username !== config.username || password !== config.password) {
|
|
31
|
+
return unauthorizedResponse
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return yield* app
|
|
35
|
+
})
|
|
36
|
+
)
|