From df03d7eda5b67642d7dbcb45d988fa832fb9c506 Mon Sep 17 00:00:00 2001 From: gzp79 <-> Date: Wed, 24 Jul 2024 17:04:13 +0200 Subject: [PATCH 1/2] add captcha validation --- .vscode/settings.json | 1 + .../regression/auth_cookie_matrix.ts | 4 +- integration-test/regression/login_oauth2.ts | 68 +- integration-test/regression/login_openid.ts | 68 +- integration-test/regression/login_token.ts | 55 +- integration-test/regression/tokens.ts | 39 +- integration-test/src/api/api.ts | 48 +- integration-test/src/api/auth_api.ts | 16 +- service/Cargo.lock | 586 ++++++++---------- service/server_config.test.json | 3 +- service/shine-service-rs | 2 +- service/src/app_config.rs | 2 + service/src/auth/auth_service.rs | 9 +- .../src/auth/auth_service_external_auth.rs | 2 +- ...ce_utils.rs => auth_service_page_utils.rs} | 22 +- service/src/auth/mod.rs | 4 +- service/src/auth/oauth2/page_oauth2_link.rs | 5 + service/src/auth/oauth2/page_oauth2_login.rs | 5 + service/src/auth/oidc/page_oidc_link.rs | 5 + service/src/auth/oidc/page_oidc_login.rs | 5 + service/src/auth/token/page_token_login.rs | 5 + service/src/main.rs | 3 + service/src/repositories/captcha_validator.rs | 188 ++++++ service/src/repositories/mod.rs | 2 + 24 files changed, 749 insertions(+), 398 deletions(-) rename service/src/auth/{auth_service_utils.rs => auth_service_page_utils.rs} (91%) create mode 100644 service/src/repositories/captcha_validator.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index f18dd93..40d1d79 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,7 @@ "hmac", "hset", "httponly", + "idempotency", "ipcity", "ipcountry", "jbang", diff --git a/integration-test/regression/auth_cookie_matrix.ts b/integration-test/regression/auth_cookie_matrix.ts index 71fc0b2..87624ee 100644 --- a/integration-test/regression/auth_cookie_matrix.ts +++ b/integration-test/regression/auth_cookie_matrix.ts @@ -53,7 +53,7 @@ describe('Auth cookie consistency matrix', () => { let tid, sid, eid: string; { - const response = await api.request.loginWithToken(null, null, null, null, true); + const response = await api.request.loginWithToken(null, null, null, null, true, null); expect(response).toHaveStatus(200); const cookies = getCookies(response); expect(cookies.tid).toBeValidTID(); @@ -64,7 +64,7 @@ describe('Auth cookie consistency matrix', () => { //eid { - const response = await api.request.linkWithOAuth2(sid); + const response = await api.request.linkWithOAuth2(sid, null); expect(response).toHaveStatus(200); const cookies = getCookies(response); expect(cookies.eid).toBeValidEID(); diff --git a/integration-test/regression/login_oauth2.ts b/integration-test/regression/login_oauth2.ts index 1b357cf..ca28e3a 100644 --- a/integration-test/regression/login_oauth2.ts +++ b/integration-test/regression/login_oauth2.ts @@ -167,10 +167,38 @@ describe('Login with OAuth2', () => { mock = undefined!; }); + it('Login without captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithOAuth2(null, null, null, undefined); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + 'https://web.sandbox.com:8080/error?type=invalidInput&status=400' + ); + expect(response.text).toContain('Failed to deserialize query string'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + + it('Login with wrong captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithOAuth2(null, null, null, 'invalid'); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + config.defaultRedirects.errorUrl + '?type=authError&status=400' + ); + expect(response.text).toContain('"Captcha":"invalid-input-response"'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + it('Start login with (token: NULL, session: VALID) shall fail', async () => { const { sid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOAuth2(null, sid, null); + const response = await api.request.loginWithOAuth2(null, sid, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -188,7 +216,7 @@ describe('Login with OAuth2', () => { const { sid } = await api.auth.loginAsGuestUser(); await api.auth.logout(sid, false); - const response = await api.request.loginWithOAuth2(null, sid, null); + const response = await api.request.loginWithOAuth2(null, sid, null, null); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock!.getUrlFor('authorize')); @@ -202,7 +230,7 @@ describe('Login with OAuth2', () => { it('Start login with (token: VALID, session: NULL) shall succeed', async () => { const { tid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOAuth2(tid, null, null); + const response = await api.request.loginWithOAuth2(tid, null, null, null); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); @@ -216,7 +244,7 @@ describe('Login with OAuth2', () => { it('Start login with (token: VALID, session: VALID) shall succeed', async () => { const { tid, sid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOAuth2(tid, sid, null); + const response = await api.request.loginWithOAuth2(tid, sid, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -297,8 +325,38 @@ describe('Link to OAuth2 account', () => { mock = undefined!; }); + it('Link without captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.linkWithOAuth2(null, undefined); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + 'https://web.sandbox.com:8080/error?type=invalidInput&status=400' + ); + expect(response.text).toContain('Failed to deserialize query string'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + + it('Link with wrong captcha shall fail and redirect to the default error page', async () => { + const user = await TestUser.createGuest(); + + const response = await api.request.linkWithOAuth2(user.sid, 'invalid'); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + config.defaultRedirects.errorUrl + '?type=authError&status=400' + ); + expect(response.text).toContain('"Captcha":"invalid-input-response"'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid.value).toEqual(user.sid); + expect(cookies.eid).toBeClearCookie(); + }); + it('Linking without a session shall fail', async () => { - const response = await api.request.linkWithOAuth2(null); + const response = await api.request.linkWithOAuth2(null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=loginRequired&status=401' diff --git a/integration-test/regression/login_openid.ts b/integration-test/regression/login_openid.ts index d927924..ff2484c 100644 --- a/integration-test/regression/login_openid.ts +++ b/integration-test/regression/login_openid.ts @@ -200,10 +200,38 @@ describe('Login with OpenId', () => { mock = undefined!; }); + it('Login without captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithOpenId(null, null, null, undefined); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + 'https://web.sandbox.com:8080/error?type=invalidInput&status=400' + ); + expect(response.text).toContain('Failed to deserialize query string'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + + it('Login with wrong captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithOpenId(null, null, null, 'invalid'); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + config.defaultRedirects.errorUrl + '?type=authError&status=400' + ); + expect(response.text).toContain('"Captcha":"invalid-input-response"'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + it('Start login with (token: NULL, session: VALID) shall fail', async () => { const { sid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOpenId(null, sid, null); + const response = await api.request.loginWithOpenId(null, sid, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -221,7 +249,7 @@ describe('Login with OpenId', () => { const { sid } = await api.auth.loginAsGuestUser(); await api.auth.logout(sid, false); - const response = await api.request.loginWithOpenId(null, sid, null); + const response = await api.request.loginWithOpenId(null, sid, null, null); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock!.getUrlFor('authorize')); @@ -235,7 +263,7 @@ describe('Login with OpenId', () => { it('Start login with (token: VALID, session: NULL) shall succeed', async () => { const { tid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOpenId(tid, null, null); + const response = await api.request.loginWithOpenId(tid, null, null, null); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); @@ -249,7 +277,7 @@ describe('Login with OpenId', () => { it('Start login with (token: VALID, session: VALID) shall succeed', async () => { const { tid, sid } = await api.auth.loginAsGuestUser(); - const response = await api.request.loginWithOpenId(tid, sid, null); + const response = await api.request.loginWithOpenId(tid, sid, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -334,8 +362,38 @@ describe('Link to OpenId account', () => { mock = undefined!; }); + it('Link without captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.linkWithOpenId(null, undefined); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + 'https://web.sandbox.com:8080/error?type=invalidInput&status=400' + ); + expect(response.text).toContain('Failed to deserialize query string'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + + it('Link with wrong captcha shall fail and redirect to the default error page', async () => { + const user = await TestUser.createGuest(); + + const response = await api.request.linkWithOpenId(user.sid, 'invalid'); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + config.defaultRedirects.errorUrl + '?type=authError&status=400' + ); + expect(response.text).toContain('"Captcha":"invalid-input-response"'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid.value).toEqual(user.sid); + expect(cookies.eid).toBeClearCookie(); + }); + it('Linking without a session shall fail', async () => { - const response = await api.request.linkWithOpenId(null); + const response = await api.request.linkWithOpenId(null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=loginRequired&status=401' diff --git a/integration-test/regression/login_token.ts b/integration-test/regression/login_token.ts index 078ab53..61ab8ea 100644 --- a/integration-test/regression/login_token.ts +++ b/integration-test/regression/login_token.ts @@ -5,9 +5,37 @@ import { TestUser } from '$lib/test_user'; import config from '../test.config'; describe('Login with token for new user', () => { + it('Login without captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithToken(null, null, null, null, null, undefined); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + 'https://web.sandbox.com:8080/error?type=invalidInput&status=400' + ); + expect(response.text).toContain('Failed to deserialize query string'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + + it('Login with wrong captcha shall fail and redirect to the default error page', async () => { + const response = await api.request.loginWithToken(null, null, null, null, null, 'invalid'); + expect(response).toHaveStatus(200); + expect(getPageRedirectUrl(response.text)).toEqual( + config.defaultRedirects.errorUrl + '?type=authError&status=400' + ); + expect(response.text).toContain('"Captcha":"invalid-input-response"'); + + const cookies = getCookies(response); + expect(cookies.tid).toBeClearCookie(); + expect(cookies.sid).toBeClearCookie(); + expect(cookies.eid).toBeClearCookie(); + }); + it('Login with (token: NO, rememberMe: INVALID) shall fail and redirect to the default error page', async () => { const response = await api.request - .loginWithToken(null, null, null, null, null) + .loginWithToken(null, null, null, null, null, null) .query({ rememberMe: 'invalid' }); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( @@ -22,7 +50,7 @@ describe('Login with token for new user', () => { }); it('Login with (token: NO, redirectMe: NO) shall fail and redirect to the login page', async () => { - const response = await api.request.loginWithToken(null, null, null, null, null); + const response = await api.request.loginWithToken(null, null, null, null, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.loginUrl); @@ -33,7 +61,7 @@ describe('Login with token for new user', () => { }); it('Login with (token: NO, rememberMe: false) shall fail and redirect to the login page', async () => { - const response = await api.request.loginWithToken(null, null, null, null, false); + const response = await api.request.loginWithToken(null, null, null, null, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.loginUrl); @@ -44,7 +72,7 @@ describe('Login with token for new user', () => { }); it('Login with (token: NONE, rememberMe: true) shall succeed and register a new user', async () => { - const response = await api.request.loginWithToken(null, null, null, null, true); + const response = await api.request.loginWithToken(null, null, null, null, true, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -89,7 +117,7 @@ describe('Login with token for returning user', () => { beforeEach(async () => { console.log('Register a new user...'); - const response = await api.request.loginWithToken(null, null, null, null, true); + const response = await api.request.loginWithToken(null, null, null, null, true, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -110,7 +138,7 @@ describe('Login with token for returning user', () => { }); it('Login with (token: NULL, session: VALID, rememberMe: true) shall fail with logout required', async () => { - const response = await api.request.loginWithToken(null, userCookies.sid, null, null, true); + const response = await api.request.loginWithToken(null, userCookies.sid, null, null, true, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -126,7 +154,14 @@ describe('Login with token for returning user', () => { }); it('Login with (token: VALID, session: VALID, rememberMe: true) shall fail with logout required', async () => { - const response = await api.request.loginWithToken(userCookies.tid, userCookies.sid, null, null, true); + const response = await api.request.loginWithToken( + userCookies.tid, + userCookies.sid, + null, + null, + true, + null + ); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=logoutRequired&status=400' @@ -143,7 +178,7 @@ describe('Login with token for returning user', () => { }); it('Login with (token: VALID, session: NULL, rememberMe: NULL) shall succeed and login the user', async () => { - const response = await api.request.loginWithToken(userCookies.tid, null, null, null, null); + const response = await api.request.loginWithToken(userCookies.tid, null, null, null, null, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -157,7 +192,7 @@ describe('Login with token for returning user', () => { }); it('Login with (token: VALID, session: NULL, rememberMe: false) shall succeed and login the user', async () => { - const response = await api.request.loginWithToken(userCookies.tid, null, null, null, false); + const response = await api.request.loginWithToken(userCookies.tid, null, null, null, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -171,7 +206,7 @@ describe('Login with token for returning user', () => { }); it('Login with (token: VALID, session: NULL, rememberMe: true) shall succeed and login the user', async () => { - const response = await api.request.loginWithToken(userCookies.tid, null, null, null, true); + const response = await api.request.loginWithToken(userCookies.tid, null, null, null, true, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); diff --git a/integration-test/regression/tokens.ts b/integration-test/regression/tokens.ts index 28d2fc2..60d3cca 100644 --- a/integration-test/regression/tokens.ts +++ b/integration-test/regression/tokens.ts @@ -179,10 +179,7 @@ describe('Tokens', () => { ]); // login shall fail with the revoked token - const responseLogin = await request - .get(config.getUrlFor('identity/auth/token/login')) - .query(config.defaultRedirects) - .set('Cookie', [`tid=${tid2}`]); + const responseLogin = await api.request.loginWithToken(tid2, null, null, null, false, null); expect(responseLogin).toHaveStatus(200); expect(getPageRedirectUrl(responseLogin.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -218,7 +215,7 @@ describe('Tokens', () => { expect(c5.rky).toEqual(c4.key); // Token rotated out shall not work - const request = await api.request.loginWithToken(tid, null, null, null, null); + const request = await api.request.loginWithToken(tid, null, null, null, null, null); expect(request).toHaveStatus(200); expect(getPageRedirectUrl(request.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -242,7 +239,8 @@ describe('Tokens', () => { null, tokenQuery.token, tokenHeader.token, - false + false, + null ); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -263,7 +261,8 @@ describe('Tokens', () => { null, null, tokenHeader.token, - false + false, + null ); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); @@ -351,7 +350,7 @@ describe('Single access token', () => { }); it('A failed login with a single access token shall clear the current user', async () => { - const response = await api.request.loginWithToken(null, null, 'invalid', null, false); + const response = await api.request.loginWithToken(null, null, 'invalid', null, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -366,7 +365,7 @@ describe('Single access token', () => { it('A successful login with a single access token shall change the current user', async () => { const token = await api.token.createSAToken(user.sid, 120, false); - const response = await api.request.loginWithToken(null, null, token.token, null, false); + const response = await api.request.loginWithToken(null, null, token.token, null, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); const cookies = getCookies(response); @@ -382,7 +381,7 @@ describe('Single access token', () => { expect(tokens).toIncludeSameMembers([token.tokenFingerprint]); const response = await api.request - .loginWithToken(null, null, token.token, null, false) + .loginWithToken(null, null, token.token, null, false, null) .set({ 'user-agent': 'agent2' }); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=authError&status=400' @@ -403,7 +402,7 @@ describe('Single access token', () => { console.log('Waiting for the token to expire (${time} second)...'); await delay(ttl * 1000); - const response = await api.request.loginWithToken(null, null, token.token, null, false); + const response = await api.request.loginWithToken(null, null, token.token, null, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -421,7 +420,7 @@ describe('Single access token', () => { const tokens = (await api.token.getTokens(user.sid)).map((x) => x.tokenFingerprint); expect(tokens).toIncludeSameMembers([token.tokenFingerprint]); - const response1 = await api.request.loginWithToken(null, null, token.token, null, false); + const response1 = await api.request.loginWithToken(null, null, token.token, null, false, null); expect(response1).toHaveStatus(200); const sid1 = getCookies(response1).sid.value; const user1 = await api.user.getUserInfo(sid1); @@ -430,7 +429,7 @@ describe('Single access token', () => { const tokens1 = (await api.token.getTokens(user.sid)).map((x) => x.tokenFingerprint); expect(tokens1, 'Token shall be removed').toIncludeSameMembers([]); - const response2 = await api.request.loginWithToken(null, null, token.token, null, false); + const response2 = await api.request.loginWithToken(null, null, token.token, null, false, null); expect(response2).toHaveStatus(200); expect(getPageRedirectUrl(response2.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -452,7 +451,7 @@ describe('Single access token', () => { const tokens1 = (await api.token.getTokens(user.sid)).map((x) => x.tokenFingerprint); expect(tokens1, 'Token shall be removed').toIncludeSameMembers([]); - const response2 = await api.request.loginWithToken(null, null, token.token, null, false); + const response2 = await api.request.loginWithToken(null, null, token.token, null, false, null); expect(response2).toHaveStatus(200); expect(getPageRedirectUrl(response2.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -479,7 +478,7 @@ describe('Persistent token', () => { it('A failed login with invalid authorization shall not change the current user', async () => { const user = await TestUser.createGuest(); const response = await api.request - .loginWithToken(user.tid!, user.sid!, null, null, false) + .loginWithToken(user.tid!, user.sid!, null, null, false, null) .set({ Authorization: `Basic invalid` }); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( @@ -495,7 +494,7 @@ describe('Persistent token', () => { }); it('A failed login with a persistent token shall clear the current user', async () => { - const response = await api.request.loginWithToken(null, null, null, 'invalid', false); + const response = await api.request.loginWithToken(null, null, null, 'invalid', false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' @@ -510,7 +509,7 @@ describe('Persistent token', () => { it('A successful login with a persistent token shall change the current user', async () => { const token = await api.token.createPersistentToken(user.sid, 120, false); - const response = await api.request.loginWithToken(null, null, null, token.token, false); + const response = await api.request.loginWithToken(null, null, null, token.token, false, null); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(config.defaultRedirects.redirectUrl); const cookies = getCookies(response); @@ -526,7 +525,7 @@ describe('Persistent token', () => { expect(tokens).toIncludeSameMembers([token.tokenFingerprint]); const response = await api.request - .loginWithToken(null, null, null, token.token, false) + .loginWithToken(null, null, null, token.token, false, null) .set({ 'user-agent': 'agent2' }); expect(getPageRedirectUrl(response.text)).toEqual( config.defaultRedirects.errorUrl + '?type=authError&status=400' @@ -543,7 +542,7 @@ describe('Persistent token', () => { expect(token.expireAt).toBeBefore(new Date(now + 130 * 1000)); for (let i = 0; i < 3; i++) { - const response1 = await api.request.loginWithToken(null, null, null, token.token, false); + const response1 = await api.request.loginWithToken(null, null, null, token.token, false, null); expect(response1).toHaveStatus(200); const sid1 = getCookies(response1).sid.value; const user1 = await api.user.getUserInfo(sid1); @@ -569,7 +568,7 @@ describe('Persistent token', () => { const tokens1 = (await api.token.getTokens(user.sid)).map((x) => x.tokenFingerprint); expect(tokens1, 'Token shall be removed').toIncludeSameMembers([]); - const response2 = await api.request.loginWithToken(null, null, null, token.token, false); + const response2 = await api.request.loginWithToken(null, null, null, token.token, false, null); expect(response2).toHaveStatus(200); expect(getPageRedirectUrl(response2.text)).toEqual( config.defaultRedirects.errorUrl + '?type=tokenExpired&status=401' diff --git a/integration-test/src/api/api.ts b/integration-test/src/api/api.ts index 1553830..0cb5f3e 100644 --- a/integration-test/src/api/api.ts +++ b/integration-test/src/api/api.ts @@ -8,6 +8,17 @@ import { UserAPI } from './user_api'; export type TokenKind = 'access' | 'persistent' | 'singleAccess'; +function getCaptchaQuery(captcha: string | null | undefined): object { + if (captcha === null) { + // use to always pass token + return { captcha: '1x00000000000000000000AA' }; + } else if (captcha) { + // use provided captcha + return { captcha }; + } + return {}; +} + export class RequestAPI { constructor(public readonly config: Config) {} @@ -26,36 +37,46 @@ export class RequestAPI { sid: string | null, queryToken: string | null, apiKey: string | null, - rememberMe: boolean | null + rememberMe: boolean | null, + captcha: string | null | undefined ): Request { const qs = rememberMe ? { rememberMe } : {}; const qt = queryToken ? { token: queryToken } : {}; const ct = tid ? [`tid=${tid}`] : []; const cs = sid ? [`sid=${sid}`] : []; const ht = apiKey ? { Authorization: `Bearer ${apiKey}` } : {}; + let qc = getCaptchaQuery(captcha); return request .get(this.config.getUrlFor('identity/auth/token/login')) - .query({ ...qs, ...qt, ...config.defaultRedirects }) + .query({ ...qs, ...qt, ...qc, ...config.defaultRedirects }) .set('Cookie', [...ct, ...cs]) .set({ ...ht }); } - loginWithOAuth2(tid: string | null, sid: string | null, rememberMe: boolean | null): Request { + loginWithOAuth2( + tid: string | null, + sid: string | null, + rememberMe: boolean | null, + captcha: string | null | undefined + ): Request { const qs = rememberMe ? { rememberMe } : {}; const ct = tid ? [`tid=${tid}`] : []; const cs = sid ? [`sid=${sid}`] : []; + let qc = getCaptchaQuery(captcha); return request .get(config.getUrlFor('identity/auth/oauth2_flow/login')) - .query({ ...qs, ...config.defaultRedirects }) + .query({ ...qs, ...qc, ...config.defaultRedirects }) .set('Cookie', [...ct, ...cs]); } - linkWithOAuth2(sid: string | null): Request { + linkWithOAuth2(sid: string | null, captcha: string | null | undefined): Request { + let qc = getCaptchaQuery(captcha); + return request .get(config.getUrlFor('identity/auth/oauth2_flow/link')) - .query({ ...config.defaultRedirects }) + .query({ ...qc, ...config.defaultRedirects }) .set('Cookie', sid ? [`sid=${sid}`] : []); } @@ -76,21 +97,28 @@ export class RequestAPI { .set('Cookie', [...cs, ...ce]); } - loginWithOpenId(tid: string | null, sid: string | null, rememberMe: boolean | null): Request { + loginWithOpenId( + tid: string | null, + sid: string | null, + rememberMe: boolean | null, + captcha: string | null | undefined + ): Request { const qs = rememberMe ? { rememberMe } : {}; const ct = tid ? [`tid=${tid}`] : []; const cs = sid ? [`sid=${sid}`] : []; + let qc = getCaptchaQuery(captcha); return request .get(config.getUrlFor('identity/auth/openid_flow/login')) - .query({ ...qs, ...config.defaultRedirects }) + .query({ ...qs, ...qc, ...config.defaultRedirects }) .set('Cookie', [...ct, ...cs]); } - linkWithOpenId(sid: string | null): Request { + linkWithOpenId(sid: string | null, captcha: string | null | undefined): Request { + let qc = getCaptchaQuery(captcha); return request .get(config.getUrlFor('identity/auth/openid_flow/link')) - .query({ ...config.defaultRedirects }) + .query({ ...qc, ...config.defaultRedirects }) .set('Cookie', sid ? [`sid=${sid}`] : []); } diff --git a/integration-test/src/api/auth_api.ts b/integration-test/src/api/auth_api.ts index 2412870..8a9421f 100644 --- a/integration-test/src/api/auth_api.ts +++ b/integration-test/src/api/auth_api.ts @@ -24,7 +24,7 @@ export class AuthAPI { async loginAsGuestUser(extraHeaders?: Record): Promise { const response = await this.request - .loginWithToken(null, null, null, null, true) + .loginWithToken(null, null, null, null, true, null) .set(extraHeaders ?? {}); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(this.config.defaultRedirects.redirectUrl); @@ -46,7 +46,7 @@ export class AuthAPI { extraHeaders?: Record ): Promise { const response = await this.request - .loginWithToken(tid, null, null, null, rememberMe) + .loginWithToken(tid, null, null, null, rememberMe, null) .set(extraHeaders ?? {}); expect(response).toHaveStatus(200); expect(getPageRedirectUrl(response.text)).toEqual(this.config.defaultRedirects.redirectUrl); @@ -69,7 +69,9 @@ export class AuthAPI { rememberMe: boolean | null, extraHeaders?: Record ): Promise { - const response = await this.request.loginWithOAuth2(null, null, rememberMe).set(extraHeaders ?? {}); + const response = await this.request + .loginWithOAuth2(null, null, rememberMe, null) + .set(extraHeaders ?? {}); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); @@ -122,7 +124,7 @@ export class AuthAPI { sid: string, extraHeaders?: Record ): Promise { - const response = await this.request.linkWithOAuth2(sid).set(extraHeaders ?? {}); + const response = await this.request.linkWithOAuth2(sid, null).set(extraHeaders ?? {}); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); @@ -172,7 +174,9 @@ export class AuthAPI { rememberMe: boolean | null, extraHeaders?: Record ): Promise { - const response = await this.request.loginWithOpenId(null, null, rememberMe).set(extraHeaders ?? {}); + const response = await this.request + .loginWithOpenId(null, null, rememberMe, null) + .set(extraHeaders ?? {}); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); @@ -230,7 +234,7 @@ export class AuthAPI { sid: string, extraHeaders?: Record ): Promise { - const response = await this.request.linkWithOpenId(sid).set(extraHeaders ?? {}); + const response = await this.request.linkWithOpenId(sid, null).set(extraHeaders ?? {}); expect(response).toHaveStatus(200); const redirectUrl = getPageRedirectUrl(response.text); expect(redirectUrl).toStartWith(mock.getUrlFor('authorize')); diff --git a/service/Cargo.lock b/service/Cargo.lock index 4b1048d..a3c0f81 100644 --- a/service/Cargo.lock +++ b/service/Cargo.lock @@ -238,9 +238,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ "async-io", "async-lock", @@ -273,7 +273,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -284,13 +284,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -307,9 +307,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7d844e282b4b56750b2d4e893b2205581ded8709fddd2b6aa5418c150ca877" +checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a2c29203f6bf296d01141cc8bb9dbd5ecd4c27843f2ee0767bcd5985a927da" +checksum = "2e89b6941c2d1a7045538884d6e760ccfffdf8e1ffc2613d8efa74305e1f3752" dependencies = [ "bindgen", "cc", @@ -345,7 +345,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "itoa", "matchit", "memchr", @@ -371,9 +371,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "itoa", "matchit", @@ -421,7 +421,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -445,7 +445,7 @@ dependencies = [ "futures-util", "headers", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -466,9 +466,9 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "pin-project-lite", "rustls 0.21.12", @@ -650,7 +650,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -663,7 +663,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.67", + "syn 2.0.72", "which", ] @@ -675,9 +675,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -735,19 +735,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "cc" -version = "1.0.99" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -777,7 +776,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -963,15 +962,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -1053,14 +1043,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -1068,27 +1058,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1120,7 +1110,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1149,7 +1139,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1210,9 +1200,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -1246,9 +1236,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "c6dc8c8ff84895b051f07a0e65f975cf225131742531338752abfb324e4449ff" dependencies = [ "log", "regex", @@ -1256,9 +1246,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "06676b12debf7bba6903559720abca942d3a66b8acb88815fd2c7c6537e9ade1" dependencies = [ "anstream", "anstyle", @@ -1477,7 +1467,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1586,7 +1576,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "ignore", "walkdir", ] @@ -1771,9 +1761,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -1788,7 +1778,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1841,9 +1831,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -1865,16 +1855,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "h2 0.4.5", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1892,7 +1882,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -1906,9 +1896,9 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", - "rustls 0.23.10", + "rustls 0.23.12", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1922,7 +1912,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1935,7 +1925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", @@ -1949,7 +1939,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "native-tls", "tokio", @@ -1959,16 +1949,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "socket2", "tokio", @@ -2116,9 +2106,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -2166,12 +2156,12 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2204,9 +2194,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matchers" @@ -2247,9 +2237,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -2272,13 +2262,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2377,16 +2368,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.2" @@ -2405,7 +2386,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2439,9 +2420,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -2492,11 +2473,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -2513,7 +2494,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2524,9 +2505,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -2534,21 +2515,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", -] - [[package]] name = "opentelemetry" version = "0.23.0" @@ -2575,10 +2541,10 @@ dependencies = [ "flate2", "http 0.2.12", "once_cell", - "opentelemetry 0.23.0", + "opentelemetry", "opentelemetry-http", "opentelemetry-semantic-conventions", - "opentelemetry_sdk 0.23.0", + "opentelemetry_sdk", "reqwest 0.11.27", "serde", "serde_json", @@ -2595,7 +2561,7 @@ dependencies = [ "async-trait", "bytes", "http 0.2.12", - "opentelemetry 0.23.0", + "opentelemetry", "reqwest 0.11.27", ] @@ -2608,9 +2574,9 @@ dependencies = [ "async-trait", "futures-core", "http 0.2.12", - "opentelemetry 0.23.0", + "opentelemetry", "opentelemetry-proto", - "opentelemetry_sdk 0.23.0", + "opentelemetry_sdk", "prost", "thiserror", "tokio", @@ -2624,8 +2590,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1a24eafe47b693cb938f8505f240dc26c71db60df9aca376b4f857e9653ec7" dependencies = [ "once_cell", - "opentelemetry 0.23.0", - "opentelemetry_sdk 0.23.0", + "opentelemetry", + "opentelemetry_sdk", "prometheus", "protobuf", ] @@ -2636,8 +2602,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" dependencies = [ - "opentelemetry 0.23.0", - "opentelemetry_sdk 0.23.0", + "opentelemetry", + "opentelemetry_sdk", "prost", "tonic", ] @@ -2657,9 +2623,9 @@ dependencies = [ "async-trait", "chrono", "futures-util", - "opentelemetry 0.23.0", - "opentelemetry_sdk 0.23.0", - "ordered-float 4.2.0", + "opentelemetry", + "opentelemetry_sdk", + "ordered-float 4.2.1", "serde", "serde_json", "thiserror", @@ -2675,10 +2641,10 @@ dependencies = [ "futures-core", "http 0.2.12", "once_cell", - "opentelemetry 0.23.0", + "opentelemetry", "opentelemetry-http", "opentelemetry-semantic-conventions", - "opentelemetry_sdk 0.23.0", + "opentelemetry_sdk", "reqwest 0.11.27", "serde", "serde_json", @@ -2686,26 +2652,6 @@ dependencies = [ "typed-builder", ] -[[package]] -name = "opentelemetry_sdk" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" -dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "once_cell", - "opentelemetry 0.22.0", - "ordered-float 4.2.0", - "percent-encoding", - "rand 0.8.5", - "thiserror", -] - [[package]] name = "opentelemetry_sdk" version = "0.23.0" @@ -2719,8 +2665,8 @@ dependencies = [ "glob", "lazy_static", "once_cell", - "opentelemetry 0.23.0", - "ordered-float 4.2.0", + "opentelemetry", + "ordered-float 4.2.1", "percent-encoding", "rand 0.8.5", "serde_json", @@ -2740,9 +2686,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.2.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be" dependencies = [ "num-traits", ] @@ -2811,9 +2757,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2864,9 +2810,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -2875,9 +2821,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -2885,22 +2831,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "pest_meta" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -2962,7 +2908,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3061,16 +3007,16 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "postgres-protocol" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" +checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "byteorder", "bytes", "fallible-iterator", @@ -3084,9 +3030,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" +checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9" dependencies = [ "bytes", "chrono", @@ -3114,7 +3060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3212,7 +3158,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3232,7 +3178,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.10", + "rustls 0.23.12", "thiserror", "tokio", "tracing", @@ -3248,7 +3194,7 @@ dependencies = [ "rand 0.8.5", "ring", "rustc-hash", - "rustls 0.23.10", + "rustls 0.23.12", "slab", "thiserror", "tinyvec", @@ -3257,14 +3203,13 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", "socket2", - "tracing", "windows-sys 0.52.0", ] @@ -3385,11 +3330,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -3418,7 +3363,7 @@ dependencies = [ "time", "tokio", "tokio-postgres", - "toml 0.8.14", + "toml 0.8.15", "url", "walkdir", ] @@ -3434,7 +3379,7 @@ dependencies = [ "quote", "refinery-core", "regex", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3495,7 +3440,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "hyper-tls 0.5.0", "ipnet", @@ -3537,9 +3482,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-rustls 0.27.2", "hyper-tls 0.6.0", "hyper-util", @@ -3552,7 +3497,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.10", + "rustls 0.23.12", "rustls-pemfile 2.1.2", "rustls-pki-types", "serde", @@ -3631,9 +3576,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -3642,22 +3587,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.67", + "syn 2.0.72", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "sha2", "walkdir", @@ -3700,7 +3645,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -3728,32 +3673,32 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile 2.1.2", @@ -3799,9 +3744,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "aws-lc-rs", "ring", @@ -3877,11 +3822,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -3890,9 +3835,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -3906,9 +3851,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -3925,20 +3870,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -3983,7 +3928,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4009,9 +3954,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", @@ -4027,14 +3972,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4050,9 +3995,9 @@ dependencies = [ [[package]] name = "sha1_smol" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" @@ -4134,7 +4079,7 @@ name = "shine-macros" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4156,14 +4101,14 @@ dependencies = [ "futures", "hex", "log", - "opentelemetry 0.23.0", + "opentelemetry", "opentelemetry-application-insights", "opentelemetry-otlp", "opentelemetry-prometheus", "opentelemetry-semantic-conventions", "opentelemetry-stdout", "opentelemetry-zipkin", - "opentelemetry_sdk 0.23.0", + "opentelemetry_sdk", "pin-project", "postgres-from-row", "primal-check", @@ -4172,7 +4117,7 @@ dependencies = [ "regex", "reqwest 0.11.27", "ring", - "rustls 0.23.10", + "rustls 0.23.12", "rustls-native-certs", "rustls-pemfile 2.1.2", "serde", @@ -4186,7 +4131,7 @@ dependencies = [ "tokio-rustls 0.26.0", "tower", "tracing", - "tracing-opentelemetry 0.24.0", + "tracing-opentelemetry", "tracing-opentelemetry-instrumentation-sdk", "tracing-subscriber", "url", @@ -4214,7 +4159,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4324,9 +4269,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -4340,9 +4285,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -4427,22 +4372,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4491,9 +4436,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -4506,21 +4451,20 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4535,13 +4479,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4556,9 +4500,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" +checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3" dependencies = [ "async-trait", "byteorder", @@ -4587,7 +4531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" dependencies = [ "ring", - "rustls 0.23.10", + "rustls 0.23.12", "tokio", "tokio-postgres", "tokio-rustls 0.26.0", @@ -4621,7 +4565,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -4661,14 +4605,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.16", ] [[package]] @@ -4693,15 +4637,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.15", ] [[package]] @@ -4718,7 +4662,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project", @@ -4757,10 +4701,10 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "pin-project-lite", "tower-layer", @@ -4800,7 +4744,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4824,24 +4768,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9be14ba1bbe4ab79e9229f7f89fab8d120b865859f10527f31c033e599d2284" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry 0.22.0", - "opentelemetry_sdk 0.22.1", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - [[package]] name = "tracing-opentelemetry" version = "0.24.0" @@ -4850,8 +4776,8 @@ checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" dependencies = [ "js-sys", "once_cell", - "opentelemetry 0.23.0", - "opentelemetry_sdk 0.23.0", + "opentelemetry", + "opentelemetry_sdk", "smallvec", "tracing", "tracing-core", @@ -4862,13 +4788,13 @@ dependencies = [ [[package]] name = "tracing-opentelemetry-instrumentation-sdk" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a276058193f1b03d8279356215ec4c8c1bb21e40e5554bb239aa94fb2d8e189" +checksum = "a7065f4c337874edb2ba504cb1e487b3bb4f1533a5bb6fcdf72da1575564814c" dependencies = [ - "opentelemetry 0.22.0", + "opentelemetry", "tracing", - "tracing-opentelemetry 0.23.0", + "tracing-opentelemetry", ] [[package]] @@ -4912,7 +4838,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -5050,12 +4976,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf8parse" version = "0.2.2" @@ -5084,7 +5004,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.67", + "syn 2.0.72", "uuid", ] @@ -5108,9 +5028,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", "serde", @@ -5143,7 +5063,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -5228,7 +5148,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -5262,7 +5182,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5295,7 +5215,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -5417,7 +5337,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5435,7 +5355,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5455,18 +5375,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -5477,9 +5397,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -5489,9 +5409,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -5501,15 +5421,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -5519,9 +5439,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -5531,9 +5451,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -5543,9 +5463,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -5555,9 +5475,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -5570,9 +5490,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" dependencies = [ "memchr", ] @@ -5642,7 +5562,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] diff --git a/service/server_config.test.json b/service/server_config.test.json index 393520f..54a6cf8 100644 --- a/service/server_config.test.json +++ b/service/server_config.test.json @@ -11,7 +11,8 @@ "allowedOrigins": [ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*sandbox\\.com(:[0-9]+)?$" ], - "fullProblemResponse": true + "fullProblemResponse": true, + "captchaSecret": "1x0000000000000000000000000000000AA" }, "telemetry": { "allowReconfigure": true, diff --git a/service/shine-service-rs b/service/shine-service-rs index a69fac5..a7bdd6f 160000 --- a/service/shine-service-rs +++ b/service/shine-service-rs @@ -1 +1 @@ -Subproject commit a69fac5e869f2a4f8922b5ff19f4b8861cc427ea +Subproject commit a7bdd6fb830015cb11d8d4250d5911e0dc7d83fc diff --git a/service/src/app_config.rs b/service/src/app_config.rs index 7c67251..2a02c1a 100644 --- a/service/src/app_config.rs +++ b/service/src/app_config.rs @@ -49,6 +49,8 @@ pub struct ServiceConfig { pub allowed_origins: Vec, /// Indicates if the full problem response should be returned. In production, it should be `false`. pub full_problem_response: bool, + /// The secret for the used captcha solution. + pub captcha_secret: String, } impl ServiceConfig { diff --git a/service/src/auth/auth_service.rs b/service/src/auth/auth_service.rs index d4e707f..d566555 100644 --- a/service/src/auth/auth_service.rs +++ b/service/src/auth/auth_service.rs @@ -1,6 +1,6 @@ use crate::{ auth::{self, AuthSessionMeta, OAuth2Client, OIDCClient, OIDCDiscoveryError}, - repositories::{AutoNameManager, IdentityManager, SessionManager}, + repositories::{AutoNameManager, CaptchaValidator, IdentityManager, SessionManager}, }; use axum::{Extension, Router}; use chrono::Duration; @@ -138,6 +138,7 @@ struct Inner { identity_manager: IdentityManager, session_manager: SessionManager, auto_name_manager: AutoNameManager, + captcha_validator: CaptchaValidator, random: SystemRandom, app_name: String, @@ -173,6 +174,10 @@ impl AuthServiceState { &self.0.auto_name_manager } + pub fn captcha_validator(&self) -> &CaptchaValidator { + &self.0.captcha_validator + } + pub fn random(&self) -> &SystemRandom { &self.0.random } @@ -219,6 +224,7 @@ pub struct AuthServiceDependencies { pub identity_manager: IdentityManager, pub session_manager: SessionManager, pub auto_name_manager: AutoNameManager, + pub captcha_validator: CaptchaValidator, } pub struct AuthServiceBuilder { @@ -272,6 +278,7 @@ impl AuthServiceBuilder { identity_manager: dependencies.identity_manager, session_manager: dependencies.session_manager, auto_name_manager: dependencies.auto_name_manager, + captcha_validator: dependencies.captcha_validator, random: SystemRandom::new(), app_name: config.app_name.to_owned(), home_url: config.home_url.to_owned(), diff --git a/service/src/auth/auth_service_external_auth.rs b/service/src/auth/auth_service_external_auth.rs index 7335b48..42be55d 100644 --- a/service/src/auth/auth_service_external_auth.rs +++ b/service/src/auth/auth_service_external_auth.rs @@ -1,6 +1,6 @@ use crate::{ auth::{ - auth_service_utils::UserCreateError, extensions, AuthError, AuthPage, AuthServiceState, AuthSession, + auth_service_page_utils::UserCreateError, extensions, AuthError, AuthPage, AuthServiceState, AuthSession, ExternalUserInfoExtensions, }, repositories::{ExternalUserInfo, IdentityError, TokenKind}, diff --git a/service/src/auth/auth_service_utils.rs b/service/src/auth/auth_service_page_utils.rs similarity index 91% rename from service/src/auth/auth_service_utils.rs rename to service/src/auth/auth_service_page_utils.rs index 5f1c441..2daa292 100644 --- a/service/src/auth/auth_service_utils.rs +++ b/service/src/auth/auth_service_page_utils.rs @@ -3,7 +3,7 @@ use crate::{ auth_session::TokenCookie, token::TokenGenerator, AuthServiceState, AuthSession, OIDCDiscoveryError, TokenGeneratorError, }, - repositories::{AutoNameError, ExternalUserInfo, Identity, IdentityError, TokenKind}, + repositories::{AutoNameError, CaptchaError, ExternalUserInfo, Identity, IdentityError, TokenKind}, }; use axum::{ http::StatusCode, @@ -146,6 +146,11 @@ pub(in crate::auth) enum AuthError { #[error("Internal server error: {0}")] InternalServerError(String), + #[error("Failed to validate captcha")] + CaptchaValidation(CaptchaError), + #[error("Captcha test failed")] + Captcha(String), + #[error("OpenId discovery failed")] OIDCDiscovery(OIDCDiscoveryError), @@ -191,6 +196,8 @@ impl AuthServiceState { AuthError::TokenExpired => ("tokenExpired", StatusCode::UNAUTHORIZED), AuthError::SessionExpired => ("sessionExpired", StatusCode::UNAUTHORIZED), AuthError::InternalServerError(_) => ("internalError", StatusCode::INTERNAL_SERVER_ERROR), + AuthError::Captcha(_) => ("authError", StatusCode::BAD_REQUEST), + AuthError::CaptchaValidation(_) => ("authError", StatusCode::INTERNAL_SERVER_ERROR), AuthError::OIDCDiscovery(_) => ("authError", StatusCode::INTERNAL_SERVER_ERROR), AuthError::ProviderAlreadyUsed => ("providerAlreadyUsed", StatusCode::CONFLICT), AuthError::EmailAlreadyUsed => ("emailAlreadyUsed", StatusCode::CONFLICT), @@ -259,4 +266,17 @@ impl AuthServiceState { html, } } + + pub(in crate::auth) async fn validate_captcha(&self, token: &str) -> Result<(), AuthError> { + match self.captcha_validator().validate(token, None).await { + Ok(result) => { + if !result.success { + Err(AuthError::Captcha(result.error_codes.join(", "))) + } else { + Ok(()) + } + } + Err(err) => Err(AuthError::CaptchaValidation(err)), + } + } } diff --git a/service/src/auth/mod.rs b/service/src/auth/mod.rs index 70022bd..3920e46 100644 --- a/service/src/auth/mod.rs +++ b/service/src/auth/mod.rs @@ -1,7 +1,7 @@ mod auth_service; pub use self::auth_service::*; -mod auth_service_utils; -pub(in crate::auth) use self::auth_service_utils::*; +mod auth_service_page_utils; +pub(in crate::auth) use self::auth_service_page_utils::*; mod auth_service_external_auth; mod auth_session; diff --git a/service/src/auth/oauth2/page_oauth2_link.rs b/service/src/auth/oauth2/page_oauth2_link.rs index cf93b6a..6294336 100644 --- a/service/src/auth/oauth2/page_oauth2_link.rs +++ b/service/src/auth/oauth2/page_oauth2_link.rs @@ -18,6 +18,7 @@ use validator::Validate; struct Query { redirect_url: Option, error_url: Option, + captcha: String, } /// Link the current user to an OAuth2 provider. @@ -36,6 +37,10 @@ async fn oauth2_link( return state.page_error(auth_session, AuthError::LoginRequired, query.error_url.as_ref()); } + if let Err(err) = state.validate_captcha(&query.captcha).await { + return state.page_error(auth_session, err, query.error_url.as_ref()); + }; + let key = match TokenGenerator::new(state.random()).generate() { Ok(key) => key, Err(err) => return state.page_internal_error(auth_session, err, query.error_url.as_ref()), diff --git a/service/src/auth/oauth2/page_oauth2_login.rs b/service/src/auth/oauth2/page_oauth2_login.rs index 0065bc4..7ec91dd 100644 --- a/service/src/auth/oauth2/page_oauth2_login.rs +++ b/service/src/auth/oauth2/page_oauth2_login.rs @@ -19,6 +19,7 @@ struct Query { redirect_url: Option, error_url: Option, remember_me: Option, + captcha: String, } /// Login or register a new user with the interactive flow using an OAuth2 provider. @@ -33,6 +34,10 @@ async fn oauth2_login( Err(error) => return state.page_error(auth_session, AuthError::InputError(error.problem), None), }; + if let Err(err) = state.validate_captcha(&query.captcha).await { + return state.page_error(auth_session, err, query.error_url.as_ref()); + }; + // Note: having a token login is not an error, on successful start of the flow, the token cookie is cleared // It has some potential issue: if tid is connected to a guest user, the guest may loose all the progress if auth_session.user_session.is_some() { diff --git a/service/src/auth/oidc/page_oidc_link.rs b/service/src/auth/oidc/page_oidc_link.rs index a69b3b0..9fb1966 100644 --- a/service/src/auth/oidc/page_oidc_link.rs +++ b/service/src/auth/oidc/page_oidc_link.rs @@ -23,6 +23,7 @@ use validator::Validate; struct Query { redirect_url: Option, error_url: Option, + captcha: String, } /// Link the current user to an OpenId Connect provider. @@ -41,6 +42,10 @@ async fn oidc_link( return state.page_error(auth_session, AuthError::LoginRequired, query.error_url.as_ref()); } + if let Err(err) = state.validate_captcha(&query.captcha).await { + return state.page_error(auth_session, err, query.error_url.as_ref()); + }; + let core_client = match client.client().await { Ok(client) => client, Err(err) => return state.page_error(auth_session, AuthError::OIDCDiscovery(err), query.error_url.as_ref()), diff --git a/service/src/auth/oidc/page_oidc_login.rs b/service/src/auth/oidc/page_oidc_login.rs index 0c8636a..70e6b48 100644 --- a/service/src/auth/oidc/page_oidc_login.rs +++ b/service/src/auth/oidc/page_oidc_login.rs @@ -24,6 +24,7 @@ struct Query { redirect_url: Option, error_url: Option, remember_me: Option, + captcha: String, } /// Login or register a new user with the interactive flow using an OpenID Connect provider. @@ -38,6 +39,10 @@ async fn oidc_login( Err(error) => return state.page_error(auth_session, AuthError::InputError(error.problem), None), }; + if let Err(err) = state.validate_captcha(&query.captcha).await { + return state.page_error(auth_session, err, query.error_url.as_ref()); + }; + // Note: having a token login is not an error, on successful start of the flow, the token cookie is cleared // It has some potential issue: if tid is connected to a guest user, the guest may loose all the progress if auth_session.user_session.is_some() { diff --git a/service/src/auth/token/page_token_login.rs b/service/src/auth/token/page_token_login.rs index 7a2e3c5..9af7187 100644 --- a/service/src/auth/token/page_token_login.rs +++ b/service/src/auth/token/page_token_login.rs @@ -30,6 +30,7 @@ struct Query { redirect_url: Option, login_url: Option, error_url: Option, + captcha: String, } struct AuthenticationResult { @@ -336,6 +337,10 @@ async fn token_login( Err(error) => return Err(state.page_error(auth_session, AuthError::InputError(error.problem), None)), }; + if let Err(err) = state.validate_captcha(&query.captcha).await { + return Err(state.page_error(auth_session, err, query.error_url.as_ref())); + }; + // clear external login cookie, it shall be only for the authorize callback from the external provider let _ = auth_session.external_login_cookie.take(); diff --git a/service/src/main.rs b/service/src/main.rs index dd8c9fb..84a20b6 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -19,6 +19,7 @@ use axum::{ use axum_server::Handle; use chrono::Duration; use openapi::ApiKind; +use repositories::CaptchaValidator; use shine_service::{ axum::{ add_default_components, telemetry::TelemetryManager, ApiEndpoint, ApiMethod, ApiPath, ApiRoute, PoweredBy, @@ -149,6 +150,7 @@ async fn async_main(_rt_handle: RtHandle) -> Result<(), AnyError> { }; let db_pool = DBPool::new(&config.db).await?; + let captcha_validator = CaptchaValidator::new(config.service.captcha_secret.clone()); let user_session = UserSessionValidator::new(None, &auth_config.session_secret, "", db_pool.redis.clone())?; let problem_config = ProblemConfig::new(config.service.full_problem_response); let identity_manager = IdentityManager::new(&db_pool.postgres).await?; @@ -169,6 +171,7 @@ async fn async_main(_rt_handle: RtHandle) -> Result<(), AnyError> { identity_manager: identity_manager.clone(), session_manager: session_manager.clone(), auto_name_manager: auto_name_manager.clone(), + captcha_validator, }; AuthServiceBuilder::new(auth_state, &config.auth) .await? diff --git a/service/src/repositories/captcha_validator.rs b/service/src/repositories/captcha_validator.rs new file mode 100644 index 0000000..b227ea0 --- /dev/null +++ b/service/src/repositories/captcha_validator.rs @@ -0,0 +1,188 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use thiserror::Error as ThisError; +use uuid::Uuid; + +const CAPTCHA_URL: &str = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; + +#[derive(Debug, ThisError, Serialize)] +pub enum CaptchaError { + #[error("Request failed with {0}")] + Request(String), +} + +#[derive(Debug, Clone, Serialize)] +pub struct TurnstileValidationRequest<'a> { + #[serde(rename = "secret")] + pub secret: &'a str, + + #[serde(rename = "response")] + pub response: &'a str, + + #[serde(rename = "remoteip")] + pub remote_ip: Option<&'a str>, + + #[serde(rename = "idempotency_key")] + pub idempotency_key: Option<&'a str>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TurnstileValidationResponse { + #[serde(rename = "success")] + pub success: bool, + + #[serde(rename = "challenge_ts")] + pub challenge_ts: Option>, + + #[serde(rename = "hostname")] + pub hostname: Option, + + #[serde(rename = "error-codes")] + pub error_codes: Vec, + + #[serde(rename = "action")] + pub action: Option, + + #[serde(rename = "cdata")] + pub cdata: Option, +} + +struct Inner { + secret: String, +} + +#[derive(Clone)] +pub struct CaptchaValidator(Arc); + +impl CaptchaValidator { + pub fn new(secret: String) -> Self { + Self(Arc::new(Inner { secret })) + } + + pub async fn validate( + &self, + token: &str, + remote_ip: Option<&str>, + ) -> Result { + let idempotency_key = Uuid::new_v4().to_string(); + + let secret = &self.0.secret; + let secret = if secret == "1x0000000000000000000000000000000AA" { + // with the testing secret allow all the test token to pass and fail everything else + let test_tokens = [ + "1x00000000000000000000AA", + "2x00000000000000000000AB", + "1x00000000000000000000BB", + "2x00000000000000000000BB", + "3x00000000000000000000FF", + ]; + if test_tokens.contains(&token) { + "1x0000000000000000000000000000000AA" + } else { + "2x0000000000000000000000000000000AA" + } + } else { + &self.0.secret + }; + + let request = TurnstileValidationRequest { + response: token, + remote_ip, + secret, + idempotency_key: Some(&idempotency_key), + }; + + let client = reqwest::Client::new(); + let response = client + .post(CAPTCHA_URL) + .form(&request) + .send() + .await + .map_err(|err| CaptchaError::Request(format!("{:?}", err)))? + .json::() + .await + .map_err(|err| CaptchaError::Request(format!("{:?}", err)))?; + Ok(response) + } +} + +#[cfg(test)] +mod test { + use super::*; + use shine_test::test; + + #[test] + async fn test_captcha_validator_test_tokens_pass() { + let validator = CaptchaValidator::new("1x0000000000000000000000000000000AA".into()); + let token = "token"; + let response = validator + .validate(token, None) + .await + .expect("Validation request failed"); + log::info!("response: {:?}", response); + assert!(response.success); + } + + #[test] + async fn test_captcha_validator_test_tokens_fail() { + let validator = CaptchaValidator::new("2x0000000000000000000000000000000AA".into()); + let token = "token"; + let response = validator + .validate(token, None) + .await + .expect("Validation request failed"); + log::info!("response: {:?}", response); + assert!(!response.success); + assert_eq!(response.error_codes, vec!["invalid-input-response"]); + } + + #[test] + async fn test_captcha_validator_test_tokens_expired() { + let validator = CaptchaValidator::new("3x0000000000000000000000000000000AA".into()); + let token = "token"; + let response = validator + .validate(token, None) + .await + .expect("Validation request failed"); + log::info!("response: {:?}", response); + assert!(!response.success); + assert_eq!(response.error_codes, vec!["timeout-or-duplicate"]); + } + + #[test] + async fn test_captcha_validator_invalid_token() { + if let Ok(secret) = std::env::var("CF_CAPTCHA_SECRET") { + let validator = CaptchaValidator::new(secret); + + let token = "invalid"; + let response = validator + .validate(token, None) + .await + .expect("Validation request failed"); + log::info!("response: {:?}", response); + assert!(!response.success); + assert_eq!(response.error_codes, vec!["invalid-input-response"]); + } else { + log::warn!("CF_CAPTCHA_SECRET not set, skipping test"); + } + } + + #[test] + async fn test_captcha_validator_expired_token() { + if let Ok(secret) = std::env::var("CF_CAPTCHA_SECRET") { + let validator = CaptchaValidator::new(secret); + + let token = "0.xVJwSGxsgonZ5dcUUCehmsoDATadFvQcJHYJ2T77vggEXA0EfzqtYQKk8dRdGgdZQieN1Cdh9TR1BCd3jU80Tkq_wBt5jdhvvMeGQNDtNRbkyj4W_Tp2_kEFRfQRWmnNA56MC2jpaNbi74OD3Ixz52koRwBkbaKWukRnHyxtQ80gkm2Uv_rnJsxFbsQurrs1JBy2azoc5zdW7esOi9gZEhwBhhXbnyj7u3Pu0Ui2ywe7ehfuU1-1dtzEMM9Gt2jSm8qYSD2AvYr2-CIUj8kIXbi5K9Z8tibclvQgePsdWo7mgMkQkpDzUKZwLpUUkqBSgP-wvcsdRS_El487aHUBjrIhVCqtaca_mCi7vIQNDSXFjzhn7_ffhzxcGZUeCj13vDjkCOcHZdtx9pJWd_G6Ir9pul0XXo60QEJJkzgxKUY3cYPaxsAhpPLvq3yfRvP7.tJWm1L0wA8I5zg2c1vPVTg.3125c6192bcc80a18596acdb789c53362fd48b71cde2ceaa0206b1e44c22f2e8"; + let response = validator + .validate(token, None) + .await + .expect("Validation request failed"); + log::info!("response: {:?}", response); + assert!(!response.success); + assert_eq!(response.error_codes, vec!["timeout-or-duplicate"]); + } else { + log::warn!("CF_CAPTCHA_SECRET not set, skipping test"); + } + } +} diff --git a/service/src/repositories/mod.rs b/service/src/repositories/mod.rs index a7ee36f..4a8751c 100644 --- a/service/src/repositories/mod.rs +++ b/service/src/repositories/mod.rs @@ -2,6 +2,8 @@ mod db; pub use self::db::*; mod auto_name_manager; pub use self::auto_name_manager::*; +mod captcha_validator; +pub use self::captcha_validator::*; mod session_manager; pub use self::session_manager::*; mod identity_manager; From 2a8d7e0114419d13f494ea34b6400ed7319f7586 Mon Sep 17 00:00:00 2001 From: gzp79 <-> Date: Wed, 24 Jul 2024 17:14:42 +0200 Subject: [PATCH 2/2] some minor fixes --- integration-test/src/api/api.ts | 2 +- service/src/repositories/captcha_validator.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-test/src/api/api.ts b/integration-test/src/api/api.ts index 0cb5f3e..9f8d814 100644 --- a/integration-test/src/api/api.ts +++ b/integration-test/src/api/api.ts @@ -10,7 +10,7 @@ export type TokenKind = 'access' | 'persistent' | 'singleAccess'; function getCaptchaQuery(captcha: string | null | undefined): object { if (captcha === null) { - // use to always pass token + // use the "always pass" token return { captcha: '1x00000000000000000000AA' }; } else if (captcha) { // use provided captcha diff --git a/service/src/repositories/captcha_validator.rs b/service/src/repositories/captcha_validator.rs index b227ea0..daddf36 100644 --- a/service/src/repositories/captcha_validator.rs +++ b/service/src/repositories/captcha_validator.rs @@ -113,9 +113,9 @@ mod test { use shine_test::test; #[test] - async fn test_captcha_validator_test_tokens_pass() { + async fn test_captcha_validator_test_token_pass() { let validator = CaptchaValidator::new("1x0000000000000000000000000000000AA".into()); - let token = "token"; + let token = "1x00000000000000000000AA"; let response = validator .validate(token, None) .await @@ -125,7 +125,7 @@ mod test { } #[test] - async fn test_captcha_validator_test_tokens_fail() { + async fn test_captcha_validator_test_token_invalid() { let validator = CaptchaValidator::new("2x0000000000000000000000000000000AA".into()); let token = "token"; let response = validator @@ -138,7 +138,7 @@ mod test { } #[test] - async fn test_captcha_validator_test_tokens_expired() { + async fn test_captcha_validator_test_token_expired() { let validator = CaptchaValidator::new("3x0000000000000000000000000000000AA".into()); let token = "token"; let response = validator @@ -151,7 +151,7 @@ mod test { } #[test] - async fn test_captcha_validator_invalid_token() { + async fn test_captcha_validator_real_token_invalid() { if let Ok(secret) = std::env::var("CF_CAPTCHA_SECRET") { let validator = CaptchaValidator::new(secret); @@ -169,7 +169,7 @@ mod test { } #[test] - async fn test_captcha_validator_expired_token() { + async fn test_captcha_validator_real_token_expired() { if let Ok(secret) = std::env::var("CF_CAPTCHA_SECRET") { let validator = CaptchaValidator::new(secret);