Skip to content

Commit 9e8efe9

Browse files
committed
Almost support for editing without login
1 parent 3422d87 commit 9e8efe9

9 files changed

Lines changed: 137 additions & 19 deletions

File tree

src/main/java/no/java/submit/config/ApplicationConfig.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import jakarta.ws.rs.Produces;
66
import org.eclipse.microprofile.config.inject.ConfigProperty;
77

8+
import java.util.Arrays;
9+
import java.util.List;
10+
811
@Dependent
912
public class ApplicationConfig {
1013

@@ -14,6 +17,9 @@ public class ApplicationConfig {
1417
@ConfigProperty(name = "app.secret", defaultValue = "JavaZoneForever")
1518
String appSecret;
1619

20+
@ConfigProperty(name = "app.admins", defaultValue = "admin@example.com")
21+
String[] appAdmins;
22+
1723
@Produces
1824
@Named("app.url")
1925
public String getAppUrl() {
@@ -25,4 +31,10 @@ public String getAppUrl() {
2531
public String getAppSecret() {
2632
return appSecret;
2733
}
34+
35+
@Produces
36+
@Named("app.admins")
37+
public List<String> getAppAdmins() {
38+
return Arrays.asList(appAdmins);
39+
}
2840
}

src/main/java/no/java/submit/controller/TalkController.java

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.quarkus.security.identity.SecurityIdentity;
77
import io.smallrye.common.annotation.Blocking;
88
import jakarta.inject.Inject;
9+
import jakarta.inject.Named;
910
import jakarta.validation.Validator;
1011
import jakarta.ws.rs.*;
1112
import jakarta.ws.rs.core.Context;
@@ -18,16 +19,18 @@
1819
import no.java.submit.service.ConferenceService;
1920
import no.java.submit.service.TalksService;
2021
import no.java.submit.service.TimelineService;
22+
import no.java.submit.util.SessionSecretHelper;
2123
import no.java.submit.util.UserHelper;
2224
import org.eclipse.microprofile.config.inject.ConfigProperty;
2325
import org.jboss.resteasy.reactive.ClientWebApplicationException;
26+
import org.jboss.resteasy.reactive.RestResponse;
2427

2528
import java.util.ArrayList;
2629
import java.util.Collections;
30+
import java.util.List;
2731
import java.util.stream.Collectors;
2832

2933
@Path("talk")
30-
@Authenticated
3134
@Blocking
3235
@Produces(MediaType.TEXT_HTML)
3336
public class TalkController {
@@ -44,6 +47,9 @@ public class TalkController {
4447
@Inject
4548
TimelineService timelineService;
4649

50+
@Inject
51+
SessionSecretHelper sessionSecrets;
52+
4753
@Inject
4854
Template talk;
4955

@@ -59,13 +65,19 @@ public class TalkController {
5965
@Inject
6066
Validator validator;
6167

68+
@Inject
69+
@Named("app.admins")
70+
List<String> appAdmins;
71+
6272
@GET
73+
@Authenticated
6374
public TemplateInstance all() {
6475
return all.instance();
6576
}
6677

6778
@GET
6879
@Path("{sessionId}")
80+
@Authenticated
6981
public TemplateInstance view(@PathParam("sessionId") String sessionId, @Context SecurityIdentity securityIdentity) {
7082
var email = UserHelper.getEmail(securityIdentity);
7183

@@ -75,14 +87,47 @@ public TemplateInstance view(@PathParam("sessionId") String sessionId, @Context
7587
if (!session.containsEmail(email))
7688
throw new NotAuthorizedException("Not allowed to view this session");
7789

78-
return talk.data("session", session);
90+
return talk
91+
.data("session", session)
92+
.data("secret", null);
93+
} catch (ClientWebApplicationException e) {
94+
throw new NotAuthorizedException("Not allowed to view this session", e);
95+
}
96+
}
97+
98+
@GET
99+
@Path("{sessionId}/{secret}")
100+
public TemplateInstance view(@PathParam("sessionId") String sessionId, @PathParam("secret") String secret) {
101+
sessionSecrets.validate(sessionId, secret);
102+
103+
try {
104+
var session = talksService.getSession("", sessionId);
105+
106+
return talk
107+
.data("session", session)
108+
.data("secret", secret);
79109
} catch (ClientWebApplicationException e) {
80110
throw new NotAuthorizedException("Not allowed to view this session", e);
81111
}
82112
}
83113

114+
@GET
115+
@Path("{sessionId}/secret")
116+
@Authenticated
117+
public RestResponse<?> redirectWithSecret(@PathParam("sessionId") String sessionId, @Context SecurityIdentity securityIdentity) {
118+
var email = UserHelper.getEmail(securityIdentity);
119+
120+
if (!appAdmins.contains(email))
121+
throw new NotAuthorizedException("Not admin");
122+
123+
return RestResponse.seeOther(UriBuilder
124+
.fromUri(String.format("/talk/%s/%s", sessionId, sessionSecrets.get(sessionId)))
125+
.build());
126+
}
127+
84128
@GET
85129
@Path("new")
130+
@Authenticated
86131
public TemplateInstance newSession(@Context SecurityIdentity securityIdentity) {
87132
if (timelineService.isClosed(UserHelper.hasExtension(securityIdentity)))
88133
return error
@@ -106,6 +151,7 @@ public TemplateInstance newSession(@Context SecurityIdentity securityIdentity) {
106151
@POST
107152
@Path("new")
108153
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
154+
@Authenticated
109155
public Object newSessionPost(SessionForm form, @Context SecurityIdentity securityIdentity) {
110156
if (timelineService.isClosed(UserHelper.hasExtension(securityIdentity)))
111157
return error
@@ -138,13 +184,26 @@ public Object newSessionPost(SessionForm form, @Context SecurityIdentity securit
138184

139185
@GET
140186
@Path("{sessionId}/edit")
187+
@Authenticated
141188
public TemplateInstance editSession(@PathParam("sessionId") String sessionId, @Context SecurityIdentity securityIdentity) {
142189
var email = UserHelper.getEmail(securityIdentity);
143190

191+
return editSession(sessionId, email, null);
192+
}
193+
194+
@GET
195+
@Path("{sessionId}/{secret}/edit")
196+
public TemplateInstance editSession(@PathParam("sessionId") String sessionId, @PathParam("secret") String secret) {
197+
sessionSecrets.validate(sessionId, secret);
198+
199+
return editSession(sessionId, "", secret);
200+
}
201+
202+
public TemplateInstance editSession(String sessionId, String email, String secret) {
144203
try {
145204
var session = talksService.getSession(email, sessionId);
146205

147-
if (!session.containsEmail(email))
206+
if (!email.isEmpty() && !session.containsEmail(email))
148207
throw new NotAuthorizedException("Not allowed to view this session");
149208

150209
if (!conferenceService.current().id.equals(session.conferenceId))
@@ -153,7 +212,8 @@ public TemplateInstance editSession(@PathParam("sessionId") String sessionId, @C
153212
return sessionForm
154213
.data("form", SessionForm.parse(session))
155214
.data("val", Collections.emptyMap())
156-
.data("sessionId", sessionId);
215+
.data("sessionId", sessionId)
216+
.data("secret", secret);
157217
} catch (ClientWebApplicationException e) {
158218
throw new NotAuthorizedException("Not allowed to view this session", e);
159219
}
@@ -162,6 +222,7 @@ public TemplateInstance editSession(@PathParam("sessionId") String sessionId, @C
162222
@POST
163223
@Path("{sessionId}/edit")
164224
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
225+
@Authenticated
165226
public Object editSessionPost(@PathParam("sessionId") String sessionId, SessionForm form, @Context SecurityIdentity securityIdentity) {
166227
// Validate form and present form if there are any errors
167228
var validation = validator.validate(form);

src/main/java/no/java/submit/service/TimelineService.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import jakarta.inject.Named;
55
import org.eclipse.microprofile.config.inject.ConfigProperty;
66

7-
import java.text.ParseException;
87
import java.time.Instant;
98
import java.time.LocalDate;
109
import java.time.LocalDateTime;
@@ -16,14 +15,17 @@
1615
@ApplicationScoped
1716
public class TimelineService {
1817

19-
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d");
18+
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d");
2019

2120
@ConfigProperty(name = "timeline.opening")
2221
public Instant opening;
2322

2423
@ConfigProperty(name = "timeline.closing")
2524
Instant closing;
2625

26+
@ConfigProperty(name = "timeline.finalized")
27+
Instant finalized;
28+
2729
@ConfigProperty(name = "timeline.feedback")
2830
Instant feedback;
2931

@@ -57,6 +59,10 @@ public boolean isClosed(boolean hasExtension) {
5759
return getClosingHard().isBefore(LocalDateTime.now());
5860
}
5961

62+
public boolean isFinalized() {
63+
return finalized != null && finalized.isBefore(Instant.now());
64+
}
65+
6066
public String format(LocalDate date) {
6167
return formatter.format(date).substring(0, 1).toUpperCase() +
6268
formatter.format(date).substring(1) +
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package no.java.submit.util;
2+
3+
import com.google.common.hash.Hashing;
4+
import com.google.common.io.BaseEncoding;
5+
import jakarta.enterprise.context.ApplicationScoped;
6+
import jakarta.inject.Inject;
7+
import jakarta.inject.Named;
8+
import jakarta.ws.rs.NotAuthorizedException;
9+
10+
@ApplicationScoped
11+
public class SessionSecretHelper {
12+
13+
@Inject
14+
@Named("app.secret")
15+
String appSecret;
16+
17+
public String get(String sessionId) {
18+
var str = String.format("%s:%s", appSecret, sessionId);
19+
return BaseEncoding.base32().omitPadding().encode(Hashing.sha256().hashBytes(str.getBytes()).asBytes()).toLowerCase();
20+
}
21+
22+
public void validate(String sessionId, String sessionSecret) {
23+
if (!get(sessionId).equals(sessionSecret)) {
24+
throw new NotAuthorizedException("Invalid session secret");
25+
}
26+
}
27+
}

src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ quarkus.cache.caffeine."sessions".expire-after-write=5m
1717
conference.current=ffbdc06b-b570-4409-bf2f-7d3b5dd2aed3
1818
timeline.opening=2025-02-14T00:00:00Z
1919
timeline.closing=2025-04-28T23:59:59Z
20+
timeline.finalized=2025-05-31T00:00:00Z
2021
timeline.feedback=2025-06-30T00:00:00Z
2122
timeline.refund=2025-08-01T00:00:00Z
2223

src/main/resources/templates/all.html

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,26 @@ <h1>All talks</h1>
1919
<h2>{conference.name}</h2>
2020

2121
{#for session in talks}
22-
<div class="session-item">
23-
<a href="/talk/{session.sessionId}">{session.data["title"]}</a>
22+
{#let tags=session:tags(session)}
23+
<div class="session-item{#if tags.contains("rejected") || tags.contains("trukket") || tags.contains("withdrawn")} out{/if}">
24+
<a href="/talk/{session.sessionId}">{session.data["title"]}</a>
2425

25-
<ul>
26-
<li>{Kind:of(session.data).name}</li>
27-
<li>{Language:of(session.data).name}</li>
28-
</ul>
29-
</div>
26+
<ul>
27+
<li>{Kind:of(session.data).name}</li>
28+
<li>{Language:of(session.data).name}</li>
29+
{#if tags.contains("rejected")}
30+
<li>Rejected</li>
31+
{#else if tags.contains("trukket") || tags.contains("withdrawn")}
32+
<li>Withdrawn</li>
33+
{#else if tags.contains("accepted") && tags.contains("confirmed")}
34+
<li>Confirmed</li>
35+
{#else if tags.contains("accepted")}
36+
<li>Accepted</li>
37+
<li><a href="https://cakeredux.javazone.no/confirm.html?id={session.sessionId}">Confirm</a></li>
38+
{/if}
39+
</ul>
40+
</div>
41+
{/let}
3042
{/for}
3143
{/if}
3244
{/for}

src/main/resources/templates/listing.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ <h2>Submitted talks</h2>
1818
</ul>
1919

2020
{#if closed}
21-
<div class="submit">
21+
<!-- <div class="submit">
2222
<p>We are past the deadline for new submissions</p>
2323
2424
<form hx-post="/login/extend" hx-target="body">
2525
<input type="text" name="code" placeholder="Code for extension" required="required"/>
2626
<button>Verify</button>
2727
</form>
28-
</div>
28+
</div> -->
2929
{/if}
3030

3131
{#if talks.isEmpty}

src/main/resources/templates/sessionForm.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ <h2>Speaker {speaker_count}</h2>
166166
</div>
167167

168168
<div class="buttons">
169-
<a href="{#if sessionId == null}/{#else}/talk/{sessionId}{/if}">Cancel</a>
169+
<a href="{#if sessionId == null}/{#else}/talk/{sessionId}{#if secret != null}/{secret}{/if}{/if}">Cancel</a>
170170
<button type="submit">Save</button>
171171
</div>
172172
</form>

src/main/resources/templates/talk.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,16 @@ <h1>{session.data["title"]}</h1>
1818
<li>Confirmed</li>
1919
{#else if tags.contains("accepted")}
2020
<li>Accepted</li>
21-
<li><a href="https://cakeredux.javazone.no/confirm.html?id={session.sessionId}">Confirm</a></li>
2221
{/if}
2322
{#if session.conferenceId == conference:current.id}
24-
<li><a href="/talk/{session.sessionId}/edit">Edit</a></li>
23+
<li><a href="/talk/{session.sessionId}{#if secret != null}/{secret}{/if}/edit">Edit</a></li>
2524
{/if}
2625
</ul>
2726

2827
{#if tags.contains("accepted") && !tags.contains("confirmed")}
2928
<div class="submit">
3029
<form>
31-
<p>This proposal accepted but not yet confirmed.</p>
30+
<p>This proposal is accepted but not yet confirmed.</p>
3231

3332
<a href="https://cakeredux.javazone.no/confirm.html?id={session.sessionId}" class="button">Confirm</a>
3433
</form>

0 commit comments

Comments
 (0)