Skip to content

Commit 33b4779

Browse files
committed
Make authentication work
1 parent 2ec6c92 commit 33b4779

40 files changed

Lines changed: 2095 additions & 731 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,5 @@ jmeter.log
158158
/venv*/
159159
/.env
160160
/ui/.angular
161+
/build/
162+
/.gradle/

CONTRIBUTING.md

Lines changed: 82 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -30,63 +30,103 @@ the feature you would like to see, why you need it, and how it should work.
3030

3131
## How to set up your environment and run tests
3232

33-
The project can be developed with Eclipse, needs a PostgreSQL database and a
34-
Redis key/value store. For authorisation, the following options are available:
35-
Google, GitHub, Facebook, Twitter, and Keycloak.
33+
The backend is a **Quarkus** application (Java 21, Gradle). The frontend is an
34+
**Angular 11** single-page app served by the
35+
[Quinoa](https://quarkiverse.github.io/quarkiverse-docs/quarkus-quinoa/dev/)
36+
extension. Authentication is handled by **Keycloak**.
3637

3738
### Prerequisites
3839

39-
For development, you need the following dependencies.
40+
| Tool | Version | Notes |
41+
|------|---------|-------|
42+
| Java JDK | 21 | Temurin / Corretto both work |
43+
| Docker + Docker Compose | any recent | runs PostgreSQL + Keycloak |
44+
| Node.js | 20 LTS | only needed for UI hot-reload; not required for the default dev flow |
4045

41-
1. Java 8 JDK (i.e. OpenJDK 8)
42-
1. Docker
43-
1. Docker Compose
44-
1. Node.js
46+
### Running it (standard — no Node required)
4547

46-
### Using IntelliJ IDEA
48+
The default workflow serves the pre-built Angular artefacts from `public/ui`
49+
directly via Quinoa. No Angular dev server or local Node installation is needed.
4750

48-
1. Run the compile SBT task
51+
1. **Start the backing services** (PostgreSQL + Keycloak):
4952

50-
```
51-
bin/activator compile
53+
```bash
54+
docker-compose up -d
5255
```
5356

54-
1. Import project in IntelliJ IDEA as a _Scala_ project
55-
1. Run the run configuration named _Start Database_
56-
1. Run the run configuration named _Start Server_
57-
1. Go to [localhost:4210/ui](http://localhost:4210/ui) to see it running
57+
2. **Copy your credentials into `.env`** (create it if it doesn't exist yet):
5858

59-
### Running it Manually
59+
```
60+
AUTH_PROVIDERS=keycloak
61+
KEYCLOAK_CLIENT_ID=translatr-localhost
62+
KEYCLOAK_CLIENT_SECRET=<your-secret>
63+
```
6064

61-
1. Run the compile SBT task
65+
3. **Start Quarkus in dev mode** — the `copyUiToBuild` task runs automatically
66+
first, staging the pre-built UI into `build/quinoa/`:
6267

68+
```bash
69+
export JAVA_HOME=/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
70+
./gradlew quarkusDev
6371
```
64-
bin/activator compile
65-
```
6672

67-
1. Run database container by using _docker compose_
73+
4. Open [http://localhost:9000](http://localhost:9000).
74+
75+
### Running it (UI hot-reload — requires Node 20 LTS)
6876

77+
Use this when you are actively working on the Angular frontend and want instant
78+
browser refresh on file save.
79+
80+
1. Complete steps 1–2 from the standard flow above.
81+
82+
2. Install UI dependencies (first time only):
83+
84+
```bash
85+
cd ui && npm install
6986
```
70-
export POSTGRES_PASSWORD=translatr
71-
docker-compose up
87+
88+
3. **Terminal A** — start the Angular dev server:
89+
90+
```bash
91+
cd ui && npm start # listens on port 4210
7292
```
7393

74-
1. Run development server
94+
4. **Terminal B** — uncomment the two `dev-server` lines in
95+
`src/main/resources/application-dev.properties`:
7596

97+
```properties
98+
%dev.quarkus.quinoa.dev-server.port=4210
99+
%dev.quarkus.quinoa.dev-server.managed=false
76100
```
77-
export AUTH_PROVIDERS=keycloak
78-
export KEYCLOAK_CLIENT_ID=translatr-localhost
79-
export KEYCLOAK_CLIENT_SECRET=$YOUR_KEYCLOAK_CLIENT_SECRET
80-
export REDIRECT_BASE=http://localhost:4210
81-
bin/activator ~run -Dconfig.file=dev.conf
101+
102+
Then start Quarkus:
103+
104+
```bash
105+
./gradlew quarkusDev
82106
```
83107

84-
1. Go to [localhost:4210/ui](http://localhost:4210/ui) to see it running
108+
5. Open [http://localhost:9000](http://localhost:9000).
109+
110+
> **Note on Node versions**
111+
> Angular 11 uses webpack 4. Node 22+ dropped `--openssl-legacy-provider`,
112+
> so **Node 20 LTS** is required for `npm start` / `npm run build`.
113+
> The `.node-version` file in `ui/` records the required version.
85114
86115
### Authorisation
87116

88-
To be able to use Translatr and create a user you need to authorise yourself.
89-
This can be done in many ways, but you need at least one auth provider.
117+
At least one auth provider must be configured. The recommended choice for local
118+
development is Keycloak (started automatically by `docker-compose up`).
119+
120+
#### Keycloak (recommended)
121+
122+
The `docker-compose.yml` starts a Keycloak instance on port **8088** and
123+
imports the `docker/Translatr-realm.json` realm automatically.
124+
125+
```
126+
export AUTH_PROVIDERS=keycloak
127+
export KEYCLOAK_CLIENT_ID=translatr-localhost
128+
export KEYCLOAK_CLIENT_SECRET=<client-secret-from-realm>
129+
```
90130

91131
#### Google
92132

@@ -108,58 +148,27 @@ export GITHUB_CLIENT_ID=...
108148
export GITHUB_CLIENT_SECRET=...
109149
```
110150

111-
#### Facebook
112-
113-
Credentials can be retrieved from the [Facebook for developers page](https://developers.facebook.com/apps).
114-
115-
```
116-
export AUTH_PROVIDERS=facebook
117-
export FACEBOOK_CLIENT_ID=...
118-
export FACEBOOK_CLIENT_SECRET=...
119-
```
120-
121-
#### Twitter
122-
123-
Credentials can be retrieved from the [Twitter Developer Documentation on OAuth](https://dev.twitter.com/docs/auth/oauth).
124-
125-
```
126-
export AUTH_PROVIDERS=twitter
127-
export TWITTER_CONSUMER_KEY=...
128-
export TWITTER_CONSUMER_SECRET=...
129-
```
130-
131-
#### Keycloak
132-
133-
The realm needs to be named Translatr. This is a sample [Keycloak app for Heroku](https://github.com/resamsel/keycloak-swarm-heroku).
134-
135-
```
136-
export KEYCLOAK_HOST=http://localhost:8080
137-
export KEYCLOAK_CLIENT_ID=...
138-
export KEYCLOAK_CLIENT_SECRET=...
139-
```
140-
141151
### Testing
142152

143-
Unit and integration tests live in the **test/** directory. In Eclipse, this
144-
directory should be a source directory.
145-
146-
Unit tests can be run by issuing the following command:
153+
Unit and integration tests use **JUnit 5** and **RestAssured** and run against
154+
the live PostgreSQL instance started by `docker-compose`.
147155

148-
```
149-
bin/activator clean jacoco:cover -Dconfig.file=test.conf -J-Xmx1g
156+
```bash
157+
./gradlew test
150158
```
151159

152-
Jacoco generates a coverage report in **target/scala-2.11/jacoco/html/index.html**
160+
The test report is written to `build/reports/tests/test/index.html`.
153161

154162
### Debugging
155163

156-
For debugging with your favorite IDE, just add the parameters `-jvm-debug 9999`
157-
to the bin/activator command. Then connect your IDE to localhost with port 9999.
164+
Add the Quarkus debug flags to attach a remote debugger on port 5005:
158165

159-
```
160-
bin/activator ~run -Dconfig.file=dev.conf -jvm-debug 9999
166+
```bash
167+
./gradlew quarkusDev -Dsuspend=false -Ddebug=5005
161168
```
162169

170+
Then connect your IDE to `localhost:5005`.
171+
163172
## How to add support for a new file format
164173

165174
Adding support for new file formats is quite easy. The few steps necessary are defined in the following sections.
@@ -323,4 +332,3 @@ GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on
323332
### Style Guide
324333

325334
Use the [Google style guide for Java](https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml).
326-

build.gradle.kts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies {
2121
implementation("io.quarkus:quarkus-hibernate-orm-panache")
2222
implementation("io.quarkus:quarkus-jdbc-postgresql")
2323
implementation("io.quarkus:quarkus-oidc")
24-
implementation("io.quarkus:quarkus-flyway-postgresql")
24+
implementation("io.quarkus:quarkus-flyway")
2525
implementation("io.quarkus:quarkus-cache")
2626
implementation("io.quarkus:quarkus-micrometer-registry-prometheus")
2727
implementation("io.quarkus:quarkus-vertx")
@@ -43,6 +43,7 @@ dependencies {
4343
testImplementation("io.quarkus:quarkus-junit5")
4444
testImplementation("io.quarkus:quarkus-junit5-mockito")
4545
testImplementation("io.quarkus:quarkus-test-security")
46+
testImplementation("io.quarkus:quarkus-test-security-jwt")
4647
testImplementation("io.rest-assured:rest-assured")
4748
testImplementation("org.assertj:assertj-core:3.15.0")
4849
testImplementation("org.mockito:mockito-core:2.8.47")
@@ -72,3 +73,29 @@ tasks.withType<Test> {
7273
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
7374
}
7475

76+
// ---------------------------------------------------------------------------
77+
// UI staging
78+
// ---------------------------------------------------------------------------
79+
// Copies the already-built Angular artefacts from public/ui into the directory
80+
// that Quinoa uses as its static-file root (build/quinoa/).
81+
// This lets `./gradlew quarkusDev` serve the UI without needing a running
82+
// Angular dev server or a Node build step.
83+
//
84+
// Usage:
85+
// ./gradlew quarkusDev ← uses pre-built files from public/ui
86+
//
87+
// To develop the UI with hot-reload instead:
88+
// 1. cd ui && npm start (Angular dev server on port 4210)
89+
// 2. In application-dev.properties uncomment the dev-server lines
90+
// ---------------------------------------------------------------------------
91+
val copyUiToBuild by tasks.registering(Copy::class) {
92+
description = "Stages pre-built Angular UI (public/ui) into Quinoa's serving directory"
93+
group = "build"
94+
from("public/ui")
95+
into("build/quinoa")
96+
}
97+
98+
tasks.named("quarkusDev") {
99+
dependsOn(copyUiToBuild)
100+
}
101+

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ services:
3333
KEYCLOAK_ADMIN: admin
3434
KEYCLOAK_ADMIN_PASSWORD: translatr123
3535
KC_DB: postgres
36-
KC_DB_URL: jdbc:postgresql://database:5432/translatr
36+
KC_DB_URL: jdbc:postgresql://database:5432/keycloak
3737
KC_DB_USERNAME: ${POSTGRES_USER:-postgres}
3838
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-translatr}
3939
volumes:

docker/Translatr-realm.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,7 @@
21032103
"clientOfflineSessionMaxLifespan": "0",
21042104
"clientSessionIdleTimeout": "0",
21052105
"clientSessionMaxLifespan": "0",
2106-
"frontendUrl": "http://localhost:8080/auth",
2106+
"frontendUrl": "http://localhost:8080",
21072107
"clientOfflineSessionIdleTimeout": "0"
21082108
},
21092109
"keycloakVersion": "11.0.2",

docker/entrypoint-initdb.d/init-translatr.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ set -e
33

44
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
55
CREATE USER translatr WITH PASSWORD 'translatr';
6-
CREATE DATABASE translatr;
76
GRANT ALL PRIVILEGES ON DATABASE translatr TO translatr;
87
CREATE DATABASE "translatr-test";
98
GRANT ALL PRIVILEGES ON DATABASE "translatr-test" TO translatr;
109
CREATE DATABASE "translatr-load-test";
1110
GRANT ALL PRIVILEGES ON DATABASE "translatr-load-test" TO translatr;
11+
CREATE USER keycloak WITH PASSWORD 'keycloak';
12+
CREATE DATABASE "keycloak";
13+
GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak;
1214
EOSQL
1315

1416
for i in translatr translatr-test translatr-load-test; do

plan-quarkusMigration.prompt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ quarkus.quinoa.ui-dir=ui
704704
quarkus.quinoa.build-dir=dist/apps/translatr
705705

706706
# Angular dev server port for live-reload proxying in dev mode
707-
quarkus.quinoa.dev-server.port=4200
707+
quarkus.quinoa.dev-server.port=4210
708708

709709
# Enable SPA routing — serves index.html for unmatched paths
710710
quarkus.quinoa.enable-spa-routing=true

src/main/java/com/translatr/auth/AccessTokenAuthMechanism.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,26 @@
44
import io.quarkus.security.identity.IdentityProviderManager;
55
import io.quarkus.security.identity.SecurityIdentity;
66
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
7-
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
87
import io.quarkus.vertx.http.runtime.security.ChallengeData;
98
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
109
import io.smallrye.mutiny.Uni;
1110
import io.vertx.ext.web.RoutingContext;
12-
import jakarta.annotation.Priority;
1311
import jakarta.enterprise.context.ApplicationScoped;
14-
import jakarta.enterprise.inject.Alternative;
1512
import jakarta.inject.Inject;
1613

17-
import java.util.Optional;
1814
import java.util.Set;
1915

2016
/**
2117
* Handles API access token authentication via:
2218
* - Header: Authorization: Bearer <token> OR X-Access-Token: <token>
2319
* - Query param: ?access_token=<token>
2420
*
25-
* Falls through (returns null) when no access token is present, letting
26-
* Quarkus OIDC handle browser/OAuth flows.
21+
* Runs alongside the Quarkus OIDC mechanism (do NOT use @Alternative here –
22+
* that would replace OIDC entirely). Returns null from both authenticate() and
23+
* getChallenge() when no access token is present, so the OIDC mechanism can
24+
* handle browser/OAuth flows and issue the Keycloak redirect.
2725
*/
2826
@ApplicationScoped
29-
@Alternative
30-
@Priority(1)
3127
public class AccessTokenAuthMechanism implements HttpAuthenticationMechanism {
3228

3329
static final String HEADER_NAME = "X-Access-Token";
@@ -49,6 +45,11 @@ public Uni<SecurityIdentity> authenticate(RoutingContext context,
4945

5046
@Override
5147
public Uni<ChallengeData> getChallenge(RoutingContext context) {
48+
if (extractToken(context) == null) {
49+
// No access token in this request – defer the challenge to the OIDC mechanism
50+
// so it can issue the browser redirect to Keycloak instead of a bare 401.
51+
return Uni.createFrom().nullItem();
52+
}
5253
return Uni.createFrom().item(
5354
new ChallengeData(401, "WWW-Authenticate", "Bearer realm=\"Translatr\""));
5455
}

src/main/java/com/translatr/controller/ActivityResource.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.translatr.controller;
22

33
import com.translatr.dto.ActivityDto;
4+
import com.translatr.dto.AggregateDto;
45
import com.translatr.dto.PagedList;
56
import com.translatr.service.ActivityService;
67
import com.translatr.service.UserService;
@@ -34,4 +35,17 @@ public PagedList<ActivityDto> byUser(@PathParam("userId") UUID userId,
3435
@QueryParam("limit") @DefaultValue("20") int limit) {
3536
return activityService.findByUser(userId, offset, limit);
3637
}
38+
39+
/**
40+
* Public aggregated activity (daily counts), used by the dashboard chart.
41+
* Optional filters: projectId, userId.
42+
*/
43+
@GET @Path("/activities/aggregated") @PermitAll
44+
public PagedList<AggregateDto> aggregated(
45+
@QueryParam("projectId") UUID projectId,
46+
@QueryParam("userId") UUID userId,
47+
@QueryParam("offset") @DefaultValue("0") int offset,
48+
@QueryParam("limit") @DefaultValue("1000") int limit) {
49+
return activityService.getAggregates(projectId, userId, offset, limit);
50+
}
3751
}

0 commit comments

Comments
 (0)