make it work
yay now I love frameworks I will make sure to use a framework every time
This commit is contained in:
parent
94250e353a
commit
63f705e693
15 changed files with 304 additions and 134 deletions
|
@ -1,19 +0,0 @@
|
||||||
package eu.m724;
|
|
||||||
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import jakarta.ws.rs.GET;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import org.jboss.resteasy.reactive.NoCache;
|
|
||||||
|
|
||||||
@Path("/api/admin")
|
|
||||||
public class AdminResource {
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@RolesAllowed("admin")
|
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
|
||||||
public String admin() {
|
|
||||||
return "You're admin";
|
|
||||||
}
|
|
||||||
}
|
|
46
src/main/java/eu/m724/GlobalAccessLimits.java
Normal file
46
src/main/java/eu/m724/GlobalAccessLimits.java
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package eu.m724;
|
||||||
|
|
||||||
|
import eu.m724.orm.AccessLimits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* some default access limits
|
||||||
|
* there are 4: (the recommends are of 2-5 minute intervals)
|
||||||
|
* kilo: 60 rph, good for 2-5 players
|
||||||
|
* mega: 120 rph, good for 4-10 players
|
||||||
|
* giga: 240 rph, good for 8-20 players
|
||||||
|
* tera: 480 rph, good for 16-40 players
|
||||||
|
* every plan has lightning streaming
|
||||||
|
* if you were to sell access you can "tweak" some of this and rip off people
|
||||||
|
*/
|
||||||
|
public class GlobalAccessLimits {
|
||||||
|
public static void initialize() {
|
||||||
|
AccessLimits kilo = new AccessLimits();
|
||||||
|
kilo.label = "kilo";
|
||||||
|
kilo.thunder = true;
|
||||||
|
kilo.weather = true;
|
||||||
|
kilo.weatherRequestsHourly = 60;
|
||||||
|
|
||||||
|
AccessLimits mega = new AccessLimits();
|
||||||
|
mega.label = "plus";
|
||||||
|
mega.thunder = true;
|
||||||
|
mega.weather = true;
|
||||||
|
mega.weatherRequestsHourly = 120;
|
||||||
|
|
||||||
|
AccessLimits giga = new AccessLimits();
|
||||||
|
giga.label = "giga";
|
||||||
|
giga.thunder = true;
|
||||||
|
giga.weather = true;
|
||||||
|
giga.weatherRequestsHourly = 240;
|
||||||
|
|
||||||
|
AccessLimits tera = new AccessLimits();
|
||||||
|
tera.label = "giga";
|
||||||
|
tera.thunder = true;
|
||||||
|
tera.weather = true;
|
||||||
|
tera.weatherRequestsHourly = 480;
|
||||||
|
|
||||||
|
kilo.persist();
|
||||||
|
mega.persist();
|
||||||
|
giga.persist();
|
||||||
|
tera.persist();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package eu.m724;
|
|
||||||
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
|
||||||
public class HelloService {
|
|
||||||
@ConfigProperty(name = "greeting")
|
|
||||||
private String greeting;
|
|
||||||
|
|
||||||
public String hello(String name) {
|
|
||||||
return greeting + " " + name;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package eu.m724;
|
|
||||||
|
|
||||||
import jakarta.annotation.security.PermitAll;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.ws.rs.GET;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
|
|
||||||
@Path("/api/public")
|
|
||||||
public class PublicResource {
|
|
||||||
@Inject
|
|
||||||
HelloService helloService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@PermitAll
|
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
|
||||||
public String hello() {
|
|
||||||
return "Hello from Quarkus REST";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@PermitAll
|
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
|
||||||
@Path("{name}")
|
|
||||||
public String namedHello(String name) {
|
|
||||||
return helloService.hello(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,26 @@
|
||||||
package eu.m724;
|
package eu.m724;
|
||||||
|
|
||||||
import eu.m724.orm.MasterKey;
|
import eu.m724.auth.master.AccountService;
|
||||||
|
import eu.m724.orm.Account;
|
||||||
import io.quarkus.runtime.StartupEvent;
|
import io.quarkus.runtime.StartupEvent;
|
||||||
import jakarta.enterprise.event.Observes;
|
import jakarta.enterprise.event.Observes;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Startup {
|
public class Startup {
|
||||||
|
@Inject
|
||||||
|
AccountService accountService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void loadUsers(@Observes StartupEvent event) {
|
public void loadUsers(@Observes StartupEvent event) {
|
||||||
MasterKey.deleteAll();
|
Account.deleteAll();
|
||||||
MasterKey.add("admin", "admin1", "admin");
|
byte[] adminKey = new byte[18];
|
||||||
MasterKey.add("user", "2user", "user");
|
|
||||||
|
UserManager.add(adminKey, "admin");
|
||||||
|
System.out.println("Admin user created: " + Base64.getEncoder().encodeToString(adminKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
src/main/java/eu/m724/UserManager.java
Normal file
65
src/main/java/eu/m724/UserManager.java
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package eu.m724;
|
||||||
|
|
||||||
|
import eu.m724.orm.AccessKey;
|
||||||
|
import eu.m724.orm.AccessLimits;
|
||||||
|
import eu.m724.orm.Account;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
// TODO figure out all this maybe move to account service
|
||||||
|
public class UserManager {
|
||||||
|
private static final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates an account with the specified key
|
||||||
|
* @param masterKey the desired master key
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public static void add(byte[] masterKey, String role) {
|
||||||
|
Account account = new Account();
|
||||||
|
account.masterKey = masterKey;
|
||||||
|
account.role = role;
|
||||||
|
account.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates an account with random key
|
||||||
|
* the account's role is "user"
|
||||||
|
* @return base64 encoded key
|
||||||
|
*/
|
||||||
|
public static String create() {
|
||||||
|
return create("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates an account with random key
|
||||||
|
* @param role new account's role
|
||||||
|
* @return base64 encoded key
|
||||||
|
*/
|
||||||
|
public static String create(String role) {
|
||||||
|
byte[] key = new byte[18]; // 144 bits of entropy
|
||||||
|
random.nextBytes(key);
|
||||||
|
|
||||||
|
add(key, role);
|
||||||
|
return Base64.getEncoder().encodeToString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generates an access key for this account
|
||||||
|
* @return base64 encoded access key
|
||||||
|
*/
|
||||||
|
public static String createMaster(Account account, AccessLimits accessLimits) {
|
||||||
|
byte[] key = new byte[18];
|
||||||
|
random.nextBytes(key);
|
||||||
|
|
||||||
|
AccessKey accessKey = new AccessKey();
|
||||||
|
accessKey.key = key;
|
||||||
|
accessKey.account = account;
|
||||||
|
accessKey.accessLimits = accessLimits;
|
||||||
|
accessKey.persist();
|
||||||
|
|
||||||
|
return Base64.getEncoder().encodeToString(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,46 @@
|
||||||
package eu.m724;
|
package eu.m724;
|
||||||
|
|
||||||
|
import eu.m724.orm.Account;
|
||||||
import io.quarkus.security.identity.SecurityIdentity;
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.json.Json;
|
||||||
|
import jakarta.json.JsonObject;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.core.Context;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.core.SecurityContext;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import org.jboss.resteasy.reactive.NoCache;
|
|
||||||
|
|
||||||
@Path("/api/users")
|
@Path("/api/users")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class UsersResource {
|
public class UsersResource {
|
||||||
|
@Inject
|
||||||
|
SecurityIdentity securityIdentity;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/create")
|
||||||
|
@RolesAllowed("admin")
|
||||||
|
public JsonObject createAccount() {
|
||||||
|
String masterKey = UserManager.create();
|
||||||
|
|
||||||
|
return Json.createObjectBuilder()
|
||||||
|
.add("masterKey", masterKey)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/me")
|
@Path("/me")
|
||||||
@RolesAllowed("user")
|
@RolesAllowed({"user", "admin"})
|
||||||
public String me(@Context SecurityContext securityContext) {
|
public JsonObject me() {
|
||||||
return securityContext.getUserPrincipal().getName();
|
Account account = securityIdentity.getAttribute("account");
|
||||||
|
String masterKey = securityIdentity.getPrincipal().getName();
|
||||||
|
|
||||||
|
String censoredKey = masterKey.substring(0, 5) + "..." + masterKey.substring(masterKey.length() - 5);
|
||||||
|
|
||||||
|
return Json.createObjectBuilder()
|
||||||
|
.add("masterKey", censoredKey)
|
||||||
|
.add("role", account.role)
|
||||||
|
//.add("accessKeys", user.accessKeys.size())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/main/java/eu/m724/auth/master/AccountService.java
Normal file
26
src/main/java/eu/m724/auth/master/AccountService.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package eu.m724.auth.master;
|
||||||
|
|
||||||
|
import eu.m724.orm.Account;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class AccountService {
|
||||||
|
/**
|
||||||
|
* find a master user by key
|
||||||
|
* @param key base64 encoded key
|
||||||
|
* @return the master user or null if wrong key or key is null
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public Account findByKey(String key) {
|
||||||
|
if (key == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Account.find("masterKey", Base64.getDecoder().decode(key)).firstResult();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package eu.m724.auth.master;
|
||||||
|
|
||||||
|
import eu.m724.orm.Account;
|
||||||
|
import io.quarkus.security.identity.IdentityProviderManager;
|
||||||
|
import io.quarkus.security.identity.SecurityIdentity;
|
||||||
|
import io.quarkus.security.runtime.QuarkusPrincipal;
|
||||||
|
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
|
||||||
|
import io.quarkus.vertx.http.runtime.security.ChallengeData;
|
||||||
|
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import io.smallrye.mutiny.infrastructure.Infrastructure;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class MyHttpAuthenticationMechanism implements HttpAuthenticationMechanism {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AccountService accountService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
|
||||||
|
String encodedKey = context.request().getHeader("X-Master-Key");
|
||||||
|
|
||||||
|
return Uni.createFrom().item(() -> {
|
||||||
|
Account account = accountService.findByKey(encodedKey);
|
||||||
|
|
||||||
|
if (account != null) {
|
||||||
|
QuarkusSecurityIdentity identity = QuarkusSecurityIdentity.builder()
|
||||||
|
.setPrincipal(new QuarkusPrincipal(encodedKey))
|
||||||
|
.addRole(account.role)
|
||||||
|
.addAttribute("account", account)
|
||||||
|
.build();
|
||||||
|
return (SecurityIdentity) identity;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).runSubscriptionOn(Infrastructure.getDefaultWorkerPool());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uni<ChallengeData> getChallenge(RoutingContext context) {
|
||||||
|
return Uni.createFrom().item(new ChallengeData(401, "WWW-Authenticate", "X-Master-Key"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,25 @@
|
||||||
package eu.m724.orm;
|
package eu.m724.orm;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
|
||||||
@Entity
|
//@Entity
|
||||||
public class AccessKey {
|
public class AccessKey extends PanacheEntity {
|
||||||
|
/**
|
||||||
|
* the user owning this access key
|
||||||
|
*/
|
||||||
|
@ManyToOne
|
||||||
|
public Account account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* raw bytes of this key, it's provided to users in base64
|
||||||
|
*/
|
||||||
|
public byte[] key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* access limits of this key
|
||||||
|
*/
|
||||||
|
@OneToOne
|
||||||
|
public AccessLimits accessLimits;
|
||||||
}
|
}
|
||||||
|
|
34
src/main/java/eu/m724/orm/AccessLimits.java
Normal file
34
src/main/java/eu/m724/orm/AccessLimits.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package eu.m724.orm;
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
|
||||||
|
//@Entity
|
||||||
|
public class AccessLimits extends PanacheEntity {
|
||||||
|
@OneToOne
|
||||||
|
public AccessKey accessKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* label of these limits, displayed to user and used to identify the limits
|
||||||
|
*/
|
||||||
|
@Column(unique = true)
|
||||||
|
public String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can this access key access live lightning data
|
||||||
|
* there's no fine controls here because it just streams everything
|
||||||
|
*/
|
||||||
|
public boolean thunder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can this access key request weather data
|
||||||
|
* there are fine controls for this
|
||||||
|
*/
|
||||||
|
public boolean weather;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* max requests per hour
|
||||||
|
*/
|
||||||
|
public int weatherRequestsHourly;
|
||||||
|
}
|
20
src/main/java/eu/m724/orm/Account.java
Normal file
20
src/main/java/eu/m724/orm/Account.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package eu.m724.orm;
|
||||||
|
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||||
|
import io.quarkus.security.jpa.Roles;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
|
||||||
|
// TODO organize all this like work on variable names move functions etc
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Account extends PanacheEntity {
|
||||||
|
@Column(unique = true)
|
||||||
|
public byte[] masterKey;
|
||||||
|
|
||||||
|
//@OneToMany
|
||||||
|
//public List<AccessKey> accessKeys = new ArrayList<>();
|
||||||
|
|
||||||
|
@Roles
|
||||||
|
public String role = "user";
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
package eu.m724.orm;
|
|
||||||
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import io.quarkus.security.jpa.RolesValue;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class LimitsTier extends PanacheEntity {
|
|
||||||
@OneToMany(mappedBy = "tier")
|
|
||||||
public List<MasterKey> masterKeys;
|
|
||||||
|
|
||||||
|
|
||||||
@RolesValue
|
|
||||||
public String role;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package eu.m724.orm;
|
|
||||||
|
|
||||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
|
||||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
|
||||||
import io.quarkus.security.jpa.Password;
|
|
||||||
import io.quarkus.security.jpa.Roles;
|
|
||||||
import io.quarkus.security.jpa.UserDefinition;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class MasterKey extends PanacheEntity {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
public long id;
|
|
||||||
|
|
||||||
public String hashedKey;
|
|
||||||
|
|
||||||
@OneToMany
|
|
||||||
public List<AccessKey> accessKeys = new ArrayList<>();
|
|
||||||
|
|
||||||
@Roles
|
|
||||||
public String role = "user";
|
|
||||||
|
|
||||||
public static void add(String key) {
|
|
||||||
MasterKey user = new MasterKey();
|
|
||||||
user.hashedKey = BcryptUtil.bcryptHash(key);
|
|
||||||
user.persist();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,3 @@
|
||||||
quarkus.http.auth.basic=true
|
|
||||||
|
|
||||||
quarkus.datasource.db.kind=h2
|
quarkus.datasource.db.kind=h2
|
||||||
|
|
||||||
quarkus.hibernate-orm.database.generation=drop-and-create
|
quarkus.hibernate-orm.database.generation=drop-and-create
|
||||||
|
|
||||||
greeting=Welcome
|
|
Loading…
Reference in a new issue