Skip to content

Commit efcea03

Browse files
committed
fix(admin): handle missing SECRET and default ipWhitelist for rate limits
1 parent 84d5f93 commit efcea03

4 files changed

Lines changed: 54 additions & 9 deletions

File tree

src/admin/password-admin-auth-provider.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,18 @@ export class PasswordAdminAuthProvider implements IAdminAuthProvider {
3232
const currentSettings = this.settings()
3333
const sessionTtlSeconds = resolveAdminSessionTtlSeconds(currentSettings.admin?.sessionTtlSeconds)
3434
const expiresAt = Math.floor(Date.now() / 1000) + sessionTtlSeconds
35-
const token = createAdminSessionToken(expiresAt)
3635

37-
response
38-
.status(200)
39-
.setHeader('content-type', 'application/json')
40-
.setHeader('Set-Cookie', buildAdminSessionCookieHeader(request, currentSettings, token, sessionTtlSeconds))
41-
.send({ authenticated: true, expiresAt })
36+
try {
37+
const token = createAdminSessionToken(expiresAt)
38+
39+
response
40+
.status(200)
41+
.setHeader('content-type', 'application/json')
42+
.setHeader('Set-Cookie', buildAdminSessionCookieHeader(request, currentSettings, token, sessionTtlSeconds))
43+
.send({ authenticated: true, expiresAt })
44+
} catch {
45+
response.status(500).setHeader('content-type', 'application/json').send({ error: 'Internal Server Error' })
46+
}
4247
}
4348

4449
public isRequestAuthenticated(request: Request): boolean {

src/routes/admin/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ const requireAdminEnabled = (_request: Request, response: Response, next: NextFu
2525
}
2626

2727
const requireAdminAuth = (request: Request, response: Response, next: NextFunction) => {
28-
if (!createAdminAuthProvider().isRequestAuthenticated(request)) {
29-
response.status(401).setHeader('content-type', 'application/json').send({ error: 'Unauthorized' })
28+
try {
29+
if (!createAdminAuthProvider().isRequestAuthenticated(request)) {
30+
response.status(401).setHeader('content-type', 'application/json').send({ error: 'Unauthorized' })
31+
return
32+
}
33+
} catch {
34+
response.status(500).setHeader('content-type', 'application/json').send({ error: 'Internal Server Error' })
3035
return
3136
}
3237

src/utils/admin-rate-limit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function isAdminRateLimited(
2424
const remoteAddress = getRemoteAddress(request, settings)
2525

2626
let limited = false
27-
if (Array.isArray(ipWhitelist) && !ipWhitelist.includes(remoteAddress)) {
27+
if (!Array.isArray(ipWhitelist) || !ipWhitelist.includes(remoteAddress)) {
2828
const rateLimiter = rateLimiterFactory()
2929
for (const { rate, period } of rateLimits) {
3030
if (await rateLimiter.hit(`${remoteAddress}:admin-${scope}:${period}`, 1, { period, rate })) {

test/unit/routes/admin.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,41 @@ describe('admin router', () => {
145145
expect(response.status).to.equal(401)
146146
})
147147

148+
it('returns 500 when SECRET is missing during login', async () => {
149+
delete process.env.SECRET
150+
process.env.ADMIN_PASSWORD = 'correct-password'
151+
const baseUrl = await startServer({ admin: { enabled: true } })
152+
153+
const response = await axios.post(
154+
`${baseUrl}/login`,
155+
{ password: 'correct-password' },
156+
{
157+
headers: { 'content-type': 'application/json' },
158+
validateStatus: () => true,
159+
},
160+
)
161+
162+
expect(response.status).to.equal(500)
163+
expect(response.data).to.deep.equal({ error: 'Internal Server Error' })
164+
165+
process.env.SECRET = 'test-admin-secret-value'
166+
})
167+
168+
it('returns 500 when SECRET is missing during session validation', async () => {
169+
delete process.env.SECRET
170+
const baseUrl = await startServer({ admin: { enabled: true } })
171+
172+
const response = await axios.get(`${baseUrl}/session`, {
173+
headers: { cookie: 'admin_session=9999999999.deadbeef' },
174+
validateStatus: () => true,
175+
})
176+
177+
expect(response.status).to.equal(500)
178+
expect(response.data).to.deep.equal({ error: 'Internal Server Error' })
179+
180+
process.env.SECRET = 'test-admin-secret-value'
181+
})
182+
148183
it('authenticates with ADMIN_PASSWORD and exposes session and health', async () => {
149184
process.env.ADMIN_PASSWORD = 'correct-password'
150185
const baseUrl = await startServer({ admin: { enabled: true, sessionTtlSeconds: 3600 } })

0 commit comments

Comments
 (0)