Compare commits

...

16 Commits

Author SHA1 Message Date
aae20169f5 Merge pull request 'Seperated composse file' () from new-oauth-server into develop
Reviewed-on: 
2025-04-29 13:26:08 +02:00
d3c8340922 Seperated composse file
Due to tests not working as expected in other environments opted to split up the
compose file to separate files. This way we can have separation of what is loaded
by each file.

We use the include directive to add the files in the order they are needed.
2025-04-29 13:25:48 +02:00
7f4ce8f0ae Merge pull request 'Expanded test to inlude oauth container' () from new-oauth-server into develop
Reviewed-on: 
2025-04-29 13:15:30 +02:00
6c4c4530e4 Expanded test to inlude oauth container 2025-04-29 13:14:45 +02:00
5a8a6ee997 Merge pull request 'Switched to new oauth server' () from new-oauth-server into develop
Reviewed-on: 
2025-04-29 12:49:19 +02:00
172669c1f2 Switched to new oauth server
We now have a new Oauth server on dsv, so we will not use toker anymore.

Also added code to update email in the db if the logged in user has an email
2025-04-29 12:46:05 +02:00
2ecdbf8d72 Merge pull request 'Removed manual check of email' () from manual-email-check into develop
Reviewed-on: 
2025-03-31 13:38:37 +02:00
316ca7c042 Removed manual check of email
New users would never be able to login because of the manual checking of email.
This has happened since the requirements and code has changed over time and we
missed the manual constraint.

This is related to the fix where we removed the database constraint.
2025-03-31 13:33:46 +02:00
a57d673c06 Merge pull request 'Remove email constratint' () from remove-email-constraint into develop
Reviewed-on: 
2025-03-31 12:32:11 +02:00
1853ecfcc3 Remove email constratint
eamil field had a unique constraint on it which had side effects.
2025-03-31 12:31:04 +02:00
35b1a306e1 Merge pull request 'Clarify about text' () from about-text-rewrite into develop
Reviewed-on: 
2025-03-27 06:49:37 +01:00
349025f200 Clarify about text
The last paragraph in the about page, stated that 'All processing is done locally ...'.
It was not clear enough where 'locally' actually was.

Changed the last paragraph to have more clarity.
The rewrite reads like this:
'All processing is done locally at the Department of Computer and Systems Sciences (DSV), Stockholm University, Sweden.
 No information is transmitted to external servers or cloud services, ensuring your privacy and data security.'
2025-03-27 06:48:19 +01:00
9bf855875b Merge pull request 'Title in head element' () from head-title-spelling into develop
Reviewed-on: 
2025-03-26 10:17:34 +01:00
b7b74f2e35 Title in head element
The title in the head element was mispelled previously
it showed 'Seshat Auido Transcriber'.

Now it's fixed to the correct spelling 'Seshat Audio Transcriber'.
2025-03-26 10:05:01 +01:00
f7466a57df Use OAuth 2.0 Token Introspection during log in ()
Currently, it uses an endpoint similar to OpenID Connect UserInfo but with some differences. The endpoint does not require the "openid" scope for example. There is an ongoing effort to replace the OAuth 2.0 authorization server with a more standard compliant one which would break the endpoint (since it would require the "openid" scope). It is currently not possible to request the "openid" scope to future-proof since Spring would act differently if that scope is present and assume full OpenID Connect. That leads to requiring an id token to have been issued which the current authorization server does not do.

To get around this the implementation is changed to use a standard compliant Token Introspection endpoint to get access to the subject of the access token (which is the only part that's necessary right now). Since the endpoint is standard compliant it will work with any future authorization server.

It may be necessary to run docker compose up --build to get the latest version of the Toker containers.

Co-authored-by: Andreas Svanberg <andreass@dsv.su.se>
Reviewed-on: 
Reviewed-by: Andreas Svanberg <andreass@dsv.su.se>
Co-authored-by: Nico Athanassiadis <nico@dsv.su.se>
Co-committed-by: Nico Athanassiadis <nico@dsv.su.se>
2025-03-25 15:48:32 +01:00
263774f74b Merge pull request 'about-and-progress-bar' () from about-and-progress-bar into develop
Reviewed-on: 
2025-03-24 14:01:23 +01:00
12 changed files with 128 additions and 27 deletions

12
compose-oauth.yaml Normal file

@ -0,0 +1,12 @@
services:
oauth2:
build:
context: https://gitea.dsv.su.se/DMC/oauth2-authorization-server.git#20cd09737d4c57bc1ee8098637cbad1a618bf49e
ports:
- '51337:8080'
environment:
- CLIENT_ID=seshat
- CLIENT_SECRET=n0tS3cr3t
- CLIENT_REDIRECT_URI=http://localhost:8181/login/oauth2/code/seshat
- CLIENT_SCOPES=openid email profile

@ -11,17 +11,8 @@ services:
volumes:
- mariadb_data:/var/lib/mysql
oauth2:
build:
context: https://github.com/dsv-su/toker.git
dockerfile: embedded.Dockerfile
ports:
- '51337:8080'
environment:
- CLIENT_ID=seshat
- CLIENT_SECRET=n0tS3cr3t
- CLIENT_REDIRECT_URI=http://localhost:8181/login/oauth2/code/seshat
include:
- compose-oauth.yaml
volumes:
mariadb_data:

@ -0,0 +1,42 @@
package se.su.dsv.seshat;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
public class TokenIntrospectionRequestEntityConverter implements Converter<OAuth2UserRequest, RequestEntity<?>> {
private static final MediaType FORM_URL_ENCODED = MediaType.valueOf(
MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"
);
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
URI uri = UriComponentsBuilder.fromUriString(
clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()
)
.build()
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
headers.setAccept(Collections.singletonList(MediaType.ALL));
headers.setContentType(FORM_URL_ENCODED);
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.TOKEN, userRequest.getAccessToken().getTokenValue());
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
}

@ -0,0 +1,25 @@
package se.su.dsv.seshat.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import se.su.dsv.seshat.TokenIntrospectionRequestEntityConverter;
@Configuration
public class SeshatConfiguration {
// Stop gap measure to switch to Token Introspection instead of OIDC UserInfo
// endpoint. This is necessary because the UserInfo endpoint will in soon require
// the "openid" scope, which is not granted to our clients. Unfortunately we can't
// request the scope because that makes Spring require an id token in the token
// exchange which is not granted at the moment.
//
// Once a new authorization server is in place we can remove this bean and use
// straight up id tokens with "openid" scope.
@Bean
public DefaultOAuth2UserService defaultOAuth2UserService() {
DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
defaultOAuth2UserService.setRequestEntityConverter(new TokenIntrospectionRequestEntityConverter());
return defaultOAuth2UserService;
}
}

@ -10,9 +10,12 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -183,6 +186,11 @@ public class FileController {
return "redirect:/files/manage";
}
@ModelAttribute("displayName")
public String getDisplayName(@AuthenticationPrincipal OAuth2User oauth2User) {
return oauth2User.getAttribute("name");
}
private static List<FileMetadata> getFileUploadStatuses(List<FileMetadata> uploaded) {
return uploaded.stream()
.filter(file -> file.getJobStatus() != null)

@ -25,7 +25,7 @@ public class AppUser {
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
@Column(nullable = false)
private String email;
@Column(nullable = false)

@ -12,6 +12,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Objects;
@Service
public class CustomOAuth2loginSuccessHandler implements AuthenticationSuccessHandler {
@ -32,11 +33,13 @@ public class CustomOAuth2loginSuccessHandler implements AuthenticationSuccessHan
String username = oAuth2User.getName();
// If the user does not have an email, set it to "no-email". We will not send any eamil notifications to this user.
String email = oAuth2User.getAttribute("mail") != null ? oAuth2User.getAttribute("mail") : "no-email";
String email = Objects.requireNonNullElse(oAuth2User.getAttribute("email"), "no-email");
if(!userService.existsByUsername(oAuth2User.getAttribute("principal"))) {
if(!userService.existsByUsername(username)) {
userService.registerUser(username, email);
} else {
userService.updateEmail(username, email);
}
response.sendRedirect(redirectUrl);
}

@ -17,9 +17,6 @@ public class UserService {
if (appUserRepository.existsByUsername(username)) {
throw new IllegalArgumentException("Username already exists");
}
if (appUserRepository.existsByEmail(email)) {
throw new IllegalArgumentException("Email already exists");
}
AppUser newUser = new AppUser(username, email, "USER");
appUserRepository.save(newUser);
@ -30,6 +27,18 @@ public class UserService {
.orElseThrow(() -> new IllegalArgumentException("User not found"));
}
public void updateEmail(String username, String newEmail) {
AppUser user = appUserRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException("User not found"));
if(newEmail.equalsIgnoreCase("no-email")) {
return;
}
user.setEmail(newEmail);
appUserRepository.save(user);
}
public boolean existsByUsername(String username) {
return appUserRepository.existsByUsername(username);
}

@ -30,12 +30,11 @@ spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
# OAuth2 properties, remember if you change the registration.provider the provider properties must be updated
spring.security.oauth2.client.provider.docker.authorization-uri=http://localhost:51337/authorize
spring.security.oauth2.client.provider.docker.token-uri=http://localhost:51337/exchange
spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:51337/verify
spring.security.oauth2.client.provider.docker.user-name-attribute=sub
spring.security.oauth2.client.provider.docker.issuer-uri=http://localhost:51337
spring.security.oauth2.client.registration.seshat.client-id=seshat
spring.security.oauth2.client.registration.seshat.client-secret=n0tS3cr3t
spring.security.oauth2.client.registration.seshat.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.seshat.provider=docker
spring.security.oauth2.client.registration.seshat.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.seshat.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.seshat.scope=openid,profile,email

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seshat Auido Transcriber</title>
<title>Seshat Audio Transcriber</title>
<link th:rel="stylesheet" th:href="@{/3p/bootstrap-5.3.3-dist/css/bootstrap.min.css}" />
<link th:rel="stylesheet" th:href="@{/3p/bootstrap-icons-1.11.3/font/bootstrap-icons.min.css}" />
<link th:rel="stylesheet" th:href="@{/css/styles.css}" />
@ -33,8 +33,10 @@
<main class="container mt-4">
<h2>About Seshat Audio Transcriber</h2>
<p>This tool allows you to upload audio files and transcribe them into text using whisperAI.</p>
<p>The application runs a local instance of <a href="https://github.com/openai/whisper" target="_blank">Whisper AI</a>, using the turbo model on one NVIDIA RTX A4000 graphics card.</p>
<p>All processing is done locally, and no data is sent to the cloud, ensuring your privacy and data security.</p>
<p>The application runs a local instance of <a href="https://github.com/openai/whisper" target="_blank">Whisper AI</a>,
using the turbo model on one NVIDIA RTX A4000 graphics card.</p>
<p>All processing is done locally at the Department of Computer and Systems Sciences (DSV), Stockholm University, Sweden.
<br>No information is transmitted to external servers or cloud services, ensuring your privacy and data security.</p>
</main>
<footer class="bg-primary text-white py-4">
<div class="container">

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seshat Auido Transcriber</title>
<title>Seshat Audio Transcriber</title>
<link th:rel="stylesheet" th:href="@{/3p/bootstrap-5.3.3-dist/css/bootstrap.min.css}" />
<link th:rel="stylesheet" th:href="@{/3p/bootstrap-icons-1.11.3/font/bootstrap-icons.min.css}" />
<link th:rel="stylesheet" th:href="@{/css/styles.css}" />
@ -17,7 +17,7 @@
<a class="user-menu text-white text-decoration-none dropdown-toggle" href="#" id="userMenu" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i>
<span th:text="${#authentication.getName()}">Username</span>
<span th:text="${displayName}">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userMenu">
<li><a class="dropdown-item" th:href="@{/logout}">Logout</a></li>

@ -3,11 +3,21 @@ package se.su.dsv.seshat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.File;
@Testcontainers
@SpringBootTest
class SeshatApplicationTests {
@ServiceConnection
private static org.testcontainers.containers.MariaDBContainer<?> dbContainer = new org.testcontainers.containers.MariaDBContainer<>("mariadb:10.11");
@Container
private static ComposeContainer oauth2Container = new ComposeContainer(new File("compose-oauth.yaml"));
@Test
void contextLoads() {
}