aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/api')
-rw-r--r--src/main/java/org/traccar/api/resource/CommandResource.java10
-rw-r--r--src/main/java/org/traccar/api/resource/PasswordResource.java35
-rw-r--r--src/main/java/org/traccar/api/resource/PositionResource.java22
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java2
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java2
-rw-r--r--src/main/java/org/traccar/api/resource/SessionResource.java29
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java13
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java4
-rw-r--r--src/main/java/org/traccar/api/security/SecurityRequestFilter.java4
-rw-r--r--src/main/java/org/traccar/api/signature/CryptoManager.java101
-rw-r--r--src/main/java/org/traccar/api/signature/KeystoreModel.java44
-rw-r--r--src/main/java/org/traccar/api/signature/TokenManager.java75
12 files changed, 309 insertions, 32 deletions
diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java
index 60f1f8eb0..636b45023 100644
--- a/src/main/java/org/traccar/api/resource/CommandResource.java
+++ b/src/main/java/org/traccar/api/resource/CommandResource.java
@@ -97,7 +97,15 @@ public class CommandResource extends ExtendedObjectResource<Command> {
@Path("send")
public Response send(Command entity) throws Exception {
permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly);
- permissionsService.checkRestriction(getUserId(), UserRestrictions::getLimitCommands);
+ if (entity.getId() > 0) {
+ permissionsService.checkPermission(Command.class, getUserId(), entity.getId());
+ long deviceId = entity.getDeviceId();
+ entity = storage.getObject(baseClass, new Request(
+ new Columns.All(), new Condition.Equals("id", "id", entity.getId())));
+ entity.setDeviceId(deviceId);
+ } else {
+ permissionsService.checkRestriction(getUserId(), UserRestrictions::getLimitCommands);
+ }
permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
if (!commandsManager.sendCommand(entity)) {
return Response.accepted(entity).build();
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
index 643471797..625ff4cb1 100644
--- a/src/main/java/org/traccar/api/resource/PasswordResource.java
+++ b/src/main/java/org/traccar/api/resource/PasswordResource.java
@@ -16,7 +16,8 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.api.signature.TokenManager;
+import org.traccar.mail.MailManager;
import org.traccar.model.User;
import org.traccar.notification.TextTemplateFormatter;
import org.traccar.storage.StorageException;
@@ -34,34 +35,34 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import java.util.UUID;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
@Path("password")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public class PasswordResource extends BaseResource {
- private static final String PASSWORD_RESET_TOKEN = "passwordToken";
-
@Inject
private MailManager mailManager;
@Inject
+ private TokenManager tokenManager;
+
+ @Inject
private TextTemplateFormatter textTemplateFormatter;
@Path("reset")
@PermitAll
@POST
- public Response reset(@FormParam("email") String email) throws StorageException, MessagingException {
+ public Response reset(@FormParam("email") String email)
+ throws StorageException, MessagingException, GeneralSecurityException, IOException {
+
User user = storage.getObject(User.class, new Request(
new Columns.All(), new Condition.Equals("email", "email", email)));
if (user != null) {
- String token = UUID.randomUUID().toString().replaceAll("-", "");
- user.set(PASSWORD_RESET_TOKEN, token);
- storage.updateObject(user, new Request(new Columns.Exclude("id"), new Condition.Equals("id", "id")));
-
var velocityContext = textTemplateFormatter.prepareContext(permissionsService.getServer(), user);
- velocityContext.put("token", token);
+ velocityContext.put("token", tokenManager.generateToken(user.getId()));
var fullMessage = textTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full");
mailManager.sendMessage(user, fullMessage.getSubject(), fullMessage.getBody());
}
@@ -72,14 +73,16 @@ public class PasswordResource extends BaseResource {
@PermitAll
@POST
public Response update(
- @FormParam("token") String token, @FormParam("password") String password) throws StorageException {
- User user = storage.getObjects(User.class, new Request(new Columns.All())).stream()
- .filter(it -> token.equals(it.getString(PASSWORD_RESET_TOKEN)))
- .findFirst().orElse(null);
+ @FormParam("token") String token, @FormParam("password") String password)
+ throws StorageException, GeneralSecurityException, IOException {
+
+ long userId = tokenManager.verifyToken(token);
+ User user = storage.getObject(User.class, new Request(
+ new Columns.All(), new Condition.Equals("id", "id", userId)));
if (user != null) {
- user.getAttributes().remove(PASSWORD_RESET_TOKEN);
user.setPassword(password);
- storage.updateObject(user, new Request(new Columns.Exclude("id"), new Condition.Equals("id", "id")));
+ storage.updateObject(user, new Request(
+ new Columns.Include("hashedPassword", "salt"), new Condition.Equals("id", "id")));
return Response.ok().build();
}
return Response.status(Response.Status.NOT_FOUND).build();
diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java
index b4c8d18b9..7d7921085 100644
--- a/src/main/java/org/traccar/api/resource/PositionResource.java
+++ b/src/main/java/org/traccar/api/resource/PositionResource.java
@@ -21,6 +21,7 @@ import org.traccar.model.Device;
import org.traccar.model.Position;
import org.traccar.model.UserRestrictions;
import org.traccar.reports.CsvExportProvider;
+import org.traccar.reports.GpxExportProvider;
import org.traccar.reports.KmlExportProvider;
import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
@@ -54,6 +55,9 @@ public class PositionResource extends BaseResource {
@Inject
private CsvExportProvider csvExportProvider;
+ @Inject
+ private GpxExportProvider gpxExportProvider;
+
@GET
public Collection<Position> getJson(
@QueryParam("deviceId") long deviceId, @QueryParam("id") List<Long> positionIds,
@@ -118,4 +122,22 @@ public class PositionResource extends BaseResource {
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.csv").build();
}
+ @Path("gpx")
+ @GET
+ @Produces("application/gpx+xml")
+ public Response getGpx(
+ @QueryParam("deviceId") long deviceId,
+ @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
+ permissionsService.checkPermission(Device.class, getUserId(), deviceId);
+ StreamingOutput stream = output -> {
+ try {
+ gpxExportProvider.generate(output, deviceId, from, to);
+ } catch (StorageException e) {
+ throw new WebApplicationException(e);
+ }
+ };
+ return Response.ok(stream)
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.gpx").build();
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 6176013c1..70177dd4d 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -19,7 +19,7 @@ package org.traccar.api.resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.helper.LogAction;
import org.traccar.model.Event;
import org.traccar.model.Position;
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 4fc76a0d7..e35cd7d95 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -16,7 +16,7 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
-import org.traccar.database.MailManager;
+import org.traccar.mail.MailManager;
import org.traccar.geocoder.Geocoder;
import org.traccar.helper.Log;
import org.traccar.helper.LogAction;
diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java
index 8eabdc63c..05f492d73 100644
--- a/src/main/java/org/traccar/api/resource/SessionResource.java
+++ b/src/main/java/org/traccar/api/resource/SessionResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@ package org.traccar.api.resource;
import org.traccar.api.BaseResource;
import org.traccar.api.security.LoginService;
+import org.traccar.api.signature.TokenManager;
import org.traccar.helper.DataConverter;
-import org.traccar.helper.ServletHelper;
import org.traccar.helper.LogAction;
+import org.traccar.helper.ServletHelper;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
import org.traccar.storage.query.Columns;
@@ -40,12 +41,14 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-
-import java.io.UnsupportedEncodingException;
+import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.Date;
@Path("session")
@Produces(MediaType.APPLICATION_JSON)
@@ -59,12 +62,15 @@ public class SessionResource extends BaseResource {
@Inject
private LoginService loginService;
- @javax.ws.rs.core.Context
+ @Inject
+ private TokenManager tokenManager;
+
+ @Context
private HttpServletRequest request;
@PermitAll
@GET
- public User get(@QueryParam("token") String token) throws StorageException, UnsupportedEncodingException {
+ public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException {
if (token != null) {
User user = loginService.login(token);
@@ -84,11 +90,11 @@ public class SessionResource extends BaseResource {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(USER_COOKIE_KEY)) {
byte[] emailBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name()));
+ URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
email = new String(emailBytes, StandardCharsets.UTF_8);
} else if (cookie.getName().equals(PASS_COOKIE_KEY)) {
byte[] passwordBytes = DataConverter.parseBase64(
- URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name()));
+ URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII));
password = new String(passwordBytes, StandardCharsets.UTF_8);
}
}
@@ -144,4 +150,11 @@ public class SessionResource extends BaseResource {
return Response.noContent().build();
}
+ @Path("token")
+ @POST
+ public String requestToken(
+ @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException {
+ return tokenManager.generateToken(getUserId(), expiration);
+ }
+
}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 104a6fac3..1e82a4cf2 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -15,6 +15,7 @@
*/
package org.traccar.api.security;
+import org.traccar.api.signature.TokenManager;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
@@ -27,29 +28,35 @@ import org.traccar.storage.query.Request;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
public class LoginService {
private final Storage storage;
+ private final TokenManager tokenManager;
private final LdapProvider ldapProvider;
private final String serviceAccountToken;
private final boolean forceLdap;
@Inject
- public LoginService(Config config, Storage storage, @Nullable LdapProvider ldapProvider) {
+ public LoginService(
+ Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) {
this.storage = storage;
+ this.tokenManager = tokenManager;
this.ldapProvider = ldapProvider;
serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN);
forceLdap = config.getBoolean(Keys.LDAP_FORCE);
}
- public User login(String token) throws StorageException {
+ public User login(String token) throws StorageException, GeneralSecurityException, IOException {
if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
return new ServiceAccountUser();
}
+ long userId = tokenManager.verifyToken(token);
User user = storage.getObject(User.class, new Request(
- new Columns.All(), new Condition.Equals("token", "token", token)));
+ new Columns.All(), new Condition.Equals("id", "id", userId)));
if (user != null) {
checkUserEnabled(user);
}
diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java
index a494c0257..ddfaaab94 100644
--- a/src/main/java/org/traccar/api/security/PermissionsService.java
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -170,8 +170,10 @@ public class PermissionsService {
|| before.getFixedEmail() != after.getFixedEmail()) {
if (userId == after.getId()) {
checkAdmin(userId);
- } else {
+ } else if (after.getId() > 0) {
checkUser(userId, after.getId());
+ } else {
+ checkManager(userId);
}
}
if (before.getFixedEmail() && !before.getEmail().equals(after.getEmail())) {
diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index ada7bf997..94b6bbf05 100644
--- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -33,8 +33,10 @@ import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
+import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
public class SecurityRequestFilter implements ContainerRequestFilter {
@@ -94,7 +96,7 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
statisticsManager.registerRequest(user.getId());
securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
}
- } catch (StorageException e) {
+ } catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
}
diff --git a/src/main/java/org/traccar/api/signature/CryptoManager.java b/src/main/java/org/traccar/api/signature/CryptoManager.java
new file mode 100644
index 000000000..8a3e7704c
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/CryptoManager.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.signature;
+
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Request;
+
+import javax.inject.Inject;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public class CryptoManager {
+
+ private final Storage storage;
+
+ private PublicKey publicKey;
+ private PrivateKey privateKey;
+
+ @Inject
+ public CryptoManager(Storage storage) {
+ this.storage = storage;
+ }
+
+ public byte[] sign(byte[] data) throws GeneralSecurityException, StorageException {
+ if (privateKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initSign(privateKey);
+ signature.update(data);
+ byte[] block = signature.sign();
+ byte[] combined = new byte[1 + block.length + data.length];
+ combined[0] = (byte) block.length;
+ System.arraycopy(block, 0, combined, 1, block.length);
+ System.arraycopy(data, 0, combined, 1 + block.length, data.length);
+ return combined;
+ }
+
+ public byte[] verify(byte[] data) throws GeneralSecurityException, StorageException {
+ if (publicKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initVerify(publicKey);
+ int length = data[0];
+ byte[] originalData = new byte[data.length - 1 - length];
+ System.arraycopy(data, 1 + length, originalData, 0, originalData.length);
+ signature.update(originalData);
+ if (!signature.verify(data, 1, length)) {
+ throw new SecurityException("Invalid signature");
+ }
+ return originalData;
+ }
+
+ private void initializeKeys() throws StorageException, GeneralSecurityException {
+ KeystoreModel model = storage.getObject(KeystoreModel.class, new Request(new Columns.All()));
+ if (model != null) {
+ publicKey = KeyFactory.getInstance("EC")
+ .generatePublic(new X509EncodedKeySpec(model.getPublicKey()));
+ privateKey = KeyFactory.getInstance("EC")
+ .generatePrivate(new PKCS8EncodedKeySpec(model.getPrivateKey()));
+ } else {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
+ generator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
+ KeyPair pair = generator.generateKeyPair();
+
+ publicKey = pair.getPublic();
+ privateKey = pair.getPrivate();
+
+ model = new KeystoreModel();
+ model.setPublicKey(publicKey.getEncoded());
+ model.setPrivateKey(privateKey.getEncoded());
+ storage.addObject(model, new Request(new Columns.Exclude("id")));
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/signature/KeystoreModel.java b/src/main/java/org/traccar/api/signature/KeystoreModel.java
new file mode 100644
index 000000000..7f3140e81
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/KeystoreModel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.signature;
+
+import org.traccar.model.BaseModel;
+import org.traccar.storage.StorageName;
+
+@StorageName("tc_keystore")
+public class KeystoreModel extends BaseModel {
+
+ private byte[] publicKey;
+
+ public byte[] getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(byte[] publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ private byte[] privateKey;
+
+ public byte[] getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(byte[] privateKey) {
+ this.privateKey = privateKey;
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/signature/TokenManager.java b/src/main/java/org/traccar/api/signature/TokenManager.java
new file mode 100644
index 000000000..a352ecc10
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/TokenManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.api.signature;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.codec.binary.Base64;
+import org.traccar.storage.StorageException;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public class TokenManager {
+
+ private static final int DEFAULT_EXPIRATION_DAYS = 7;
+
+ private final ObjectMapper objectMapper;
+ private final CryptoManager cryptoManager;
+
+ public static class Data {
+ @JsonProperty("u")
+ private long userId;
+ @JsonProperty("e")
+ private Date expiration;
+ }
+
+ @Inject
+ public TokenManager(ObjectMapper objectMapper, CryptoManager cryptoManager) {
+ this.objectMapper = objectMapper;
+ this.cryptoManager = cryptoManager;
+ }
+
+ public String generateToken(long userId) throws IOException, GeneralSecurityException, StorageException {
+ return generateToken(userId, null);
+ }
+
+ public String generateToken(
+ long userId, Date expiration) throws IOException, GeneralSecurityException, StorageException {
+ Data data = new Data();
+ data.userId = userId;
+ if (expiration != null) {
+ data.expiration = expiration;
+ } else {
+ data.expiration = new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(DEFAULT_EXPIRATION_DAYS));
+ }
+ byte[] encoded = objectMapper.writeValueAsBytes(data);
+ return Base64.encodeBase64URLSafeString(cryptoManager.sign(encoded));
+ }
+
+ public long verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
+ byte[] encoded = cryptoManager.verify(Base64.decodeBase64(token));
+ Data data = objectMapper.readValue(encoded, Data.class);
+ if (data.expiration.before(new Date())) {
+ throw new SecurityException("Token has expired");
+ }
+ return data.userId;
+ }
+
+}