Skip to content

Commit 736aacb

Browse files
committed
Support simple auth
1 parent 5f6b1df commit 736aacb

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

tsunagu-server/src/main/java/am/ik/tsunagu/TsunaguController.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.util.List;
1111
import java.util.Map;
1212
import java.util.Objects;
13-
import java.util.Set;
1413
import java.util.UUID;
1514
import java.util.concurrent.ConcurrentHashMap;
1615
import java.util.concurrent.ConcurrentMap;
@@ -40,6 +39,9 @@
4039
import org.springframework.messaging.handler.annotation.MessageMapping;
4140
import org.springframework.messaging.rsocket.RSocketRequester;
4241
import org.springframework.messaging.rsocket.annotation.ConnectMapping;
42+
import org.springframework.util.Base64Utils;
43+
import org.springframework.util.StringUtils;
44+
import org.springframework.web.bind.annotation.ExceptionHandler;
4345
import org.springframework.web.bind.annotation.GetMapping;
4446
import org.springframework.web.bind.annotation.PathVariable;
4547
import org.springframework.web.bind.annotation.RequestMapping;
@@ -96,6 +98,7 @@ public Collection<UUID> requesters() {
9698

9799
@RequestMapping(path = "**")
98100
public Mono<Void> proxy(ServerHttpRequest request, ServerHttpResponse response) throws Exception {
101+
this.checkAuthorization(request);
99102
final HttpHeaders httpHeaders = setForwardHeaders(request);
100103
final HttpRequestMetadata httpRequestMetadata = new HttpRequestMetadata(request.getMethod(), request.getURI(), httpHeaders);
101104
final Flux<DataBuffer> responseStream;
@@ -230,8 +233,48 @@ else if ("https".equals(scheme) || "wss".equals(scheme)) {
230233
httpHeaders.addAll(source);
231234
final String remoteAddress = request.getRemoteAddress().getAddress().getHostAddress();
232235
final String forwarded = String.format("for=%s;host=%s:%d;proto=%s", remoteAddress, uri.getHost(), port, scheme);
233-
httpHeaders.set("Forwarded", forwarded);
234-
httpHeaders.set("X-Real-IP", remoteAddress);
235-
return httpHeaders;
236+
httpHeaders.set("Forwarded", forwarded); httpHeaders.set("X-Real-IP", remoteAddress); return httpHeaders;
237+
}
238+
239+
240+
@ExceptionHandler(AuthorizationException.class)
241+
public Mono<ResponseEntity<Map<String, ?>>> handleAuthorizationException(AuthorizationException e) {
242+
return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED)
243+
.header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Tsunagu API\"")
244+
.body(Map.of("error", Map.of("message", e.getMessage(), "type", "invalid_request_error", "code", e.code))));
245+
}
246+
247+
void checkAuthorization(ServerHttpRequest request) {
248+
if (StringUtils.hasText(this.props.getAuthorizationToken())) {
249+
final String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
250+
if (StringUtils.hasText(authorization)) {
251+
if (authorization.startsWith("Bearer") || authorization.startsWith("bearer")) {
252+
final String token = authorization.replace("Bearer ", "").replace("bearer ", "");
253+
if (!Objects.equals(this.props.getAuthorizationToken(), token)) {
254+
throw new AuthorizationException("Incorrect API key provided: " + token, "invalid_api_key");
255+
}
256+
}
257+
else if (authorization.startsWith("Basic") || authorization.startsWith("basic")) {
258+
final String basic = authorization.replace("Basic ", "").replace("basic ", "");
259+
final String token = new String(Base64Utils.decodeFromString(basic)).split(":", 2)[1];
260+
if (!Objects.equals(this.props.getAuthorizationToken(), token)) {
261+
throw new AuthorizationException("Incorrect API key provided: " + token, "invalid_api_key");
262+
}
263+
}
264+
}
265+
else {
266+
throw new AuthorizationException("You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accesing the API from your browser and are prompted for a username and password.", "");
267+
}
268+
}
269+
}
270+
271+
public static class AuthorizationException extends RuntimeException {
272+
273+
274+
private final String code;
275+
276+
public AuthorizationException(String message, String code) {
277+
super(message); this.code = code;
278+
}
236279
}
237280
}

tsunagu-server/src/main/java/am/ik/tsunagu/TsunaguProps.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
public class TsunaguProps {
2020
private final String token;
2121

22+
private final String authorizationToken;
23+
2224
private final Tls tls;
2325

2426
private final Map<String, String> acmeChallenge;
2527

2628
private final Logger log = LoggerFactory.getLogger(TsunaguProps.class);
2729

28-
public TsunaguProps(String token, Tls tls, Map<String, String> acmeChallenge) {
30+
public TsunaguProps(String token, String authorizationToken, Tls tls, Map<String, String> acmeChallenge) {
2931
this.tls = tls;
32+
this.authorizationToken = authorizationToken;
3033
this.acmeChallenge = acmeChallenge;
3134
if (token == null) {
3235
this.token = UUID.randomUUID().toString();
@@ -41,6 +44,10 @@ public String getToken() {
4144
return token;
4245
}
4346

47+
public String getAuthorizationToken() {
48+
return authorizationToken;
49+
}
50+
4451
public Tls getTls() {
4552
return tls;
4653
}

0 commit comments

Comments
 (0)