cleanup urls, nuke hangar user principal

This commit is contained in:
MiniDigger | Martin 2023-03-28 00:53:33 +02:00
parent ac8fa50fd6
commit d26589f1e1
16 changed files with 94 additions and 109 deletions

View File

@ -5,4 +5,4 @@
<file url="file://$PROJECT_DIR$/backend/src/main/resources" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
</project>

View File

@ -52,7 +52,7 @@ public class LoginController extends HangarComponent {
this.config.checkDev();
final UserTable fakeUser = this.authenticationService.loginAsFakeUser();
this.tokenService.issueRefreshToken(fakeUser, this.response);
this.tokenService.issueRefreshToken(fakeUser.getUserId(), this.response);
return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(returnUrl));
} else {
this.response.addCookie(new Cookie("url", this.cutoffAbsoluteUrls(returnUrl)));
@ -71,7 +71,7 @@ public class LoginController extends HangarComponent {
if (!this.validationService.isValidUsername(user.getName())) {
throw new HangarApiException("nav.user.error.invalidUsername");
}
this.tokenService.issueRefreshToken(user, this.response);
this.tokenService.issueRefreshToken(user.getUserId(), this.response);
return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(url));
}

View File

@ -11,7 +11,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -20,7 +19,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
@RateLimit(path = "auth")
@RequestMapping(path = "/api/internal", produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(path = "/api/internal/auth", produces = MediaType.APPLICATION_JSON_VALUE)
public class AuthController extends HangarComponent {
private final HangarWebAuthnAuthenticatorService hangarWebAuthnAuthenticatorService;
@ -29,7 +28,7 @@ public class AuthController extends HangarComponent {
this.hangarWebAuthnAuthenticatorService = hangarWebAuthnAuthenticatorService;
}
@PostMapping("/auth/signup")
@PostMapping("/signup")
public void signup() {
}

View File

@ -3,19 +3,27 @@ package io.papermc.hangar.security.authentication;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.projects.ProjectOwner;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class HangarPrincipal implements ProjectOwner, Serializable {
public class HangarPrincipal implements ProjectOwner, UserDetails, Serializable {
private final long id;
private final String name;
private final boolean locked;
private final Permission globalPermissions;
private int aal = -1;
private final String password;
public HangarPrincipal(final long id, final String name, final boolean locked, final Permission globalPermissions) {
public HangarPrincipal(final long id, final String name, final boolean locked, final Permission globalPermissions, final String password) {
this.id = id;
this.name = name;
this.locked = locked;
this.globalPermissions = globalPermissions;
this.password = password;
}
@Override
@ -45,6 +53,14 @@ public class HangarPrincipal implements ProjectOwner, Serializable {
return this.globalPermissions.intersect(this.getPossiblePermissions());
}
public int getAal() {
return this.aal;
}
public void setAal(final int aal) {
this.aal = aal;
}
public final boolean isAllowedGlobal(final Permission requiredPermission) {
return this.isAllowed(requiredPermission, this.globalPermissions);
}
@ -57,6 +73,41 @@ public class HangarPrincipal implements ProjectOwner, Serializable {
return this.getPossiblePermissions().has(requiredPermission.intersect(currentPermission));
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("dum"));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.getName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !this.isLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return !this.isLocked();
}
@Override
public String toString() {
return "HangarPrincipal{" +
@ -64,6 +115,7 @@ public class HangarPrincipal implements ProjectOwner, Serializable {
", name='" + this.name + '\'' +
", locked=" + this.locked +
", globalPermissions=" + this.globalPermissions +
", aal=" + this.aal +
'}';
}
}

View File

@ -9,7 +9,7 @@ public class HangarApiPrincipal extends HangarPrincipal {
private final ApiKeyTable apiKeyTable;
public HangarApiPrincipal(final long id, final String name, final boolean locked, final Permission globalPermissions, final ApiKeyTable apiKeyTable) {
super(id, name, locked, globalPermissions);
super(id, name, locked, globalPermissions, null);
this.apiKeyTable = apiKeyTable;
}

View File

@ -1,77 +0,0 @@
package io.papermc.hangar.security.authentication.user;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.security.authentication.HangarPrincipal;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class HangarUserPrincipal extends HangarPrincipal implements UserDetails {
private final UserTable userTable;
private final String password;
private int aal = -1;
public HangarUserPrincipal(final UserTable userTable, final String password, final Permission globalPermissions) {
super(userTable.getUserId(), userTable.getName(), userTable.isLocked(), globalPermissions);
this.userTable = userTable;
this.password = password;
}
public UserTable getUserTable() {
return this.userTable;
}
public int getAal() {
return this.aal;
}
public void setAal(final int aal) {
this.aal = aal;
}
@Override
public String toString() {
return "HangarUserPrincipal{" +
"userTable=" + this.userTable +
"} " + super.toString();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("dum"));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.getName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !this.isLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return !this.isLocked();
}
}

View File

@ -93,7 +93,7 @@ public class SecurityConfig {
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(final UserDetailsService userDetailsService){
public DaoAuthenticationProvider daoAuthenticationProvider(final UserDetailsService userDetailsService) {
final DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(this.passwordEncoder());

View File

@ -27,7 +27,7 @@ import com.webauthn4j.springframework.security.options.RpIdProvider;
import com.webauthn4j.springframework.security.options.RpIdProviderImpl;
import com.webauthn4j.springframework.security.server.ServerPropertyProvider;
import io.papermc.hangar.security.authentication.HangarAuthenticationEntryPoint;
import io.papermc.hangar.security.authentication.user.HangarUserPrincipal;
import io.papermc.hangar.security.authentication.HangarPrincipal;
import io.papermc.hangar.service.TokenService;
import io.papermc.hangar.service.internal.auth.HangarPublicKeyCredentialUserEntityProvider;
import io.papermc.hangar.service.internal.auth.HangarUserDetailService;
@ -51,20 +51,26 @@ public class WebAuthnConfig {
this.entryPoint = entryPoint;
}
// TODO do we need to save aal into jwt?
// prolly update the jwp in response to login endpoint
// TODO need to figure out how to handle aal + api keys
public void configure(final HttpSecurity http, final AuthenticationManager authenticationManager) throws Exception {
http.apply(WebAuthnLoginConfigurer.webAuthnLogin())
.loginProcessingUrl("/api/internal/auth/login")
.successHandler((request, response, authentication) -> {
if (authentication instanceof final UsernamePasswordAuthenticationToken token) {
if (token.getPrincipal() instanceof final HangarUserPrincipal principal) {
this.tokenService.issueRefreshToken(principal.getUserTable(), response);
principal.setAal(1); // check if email verified, if not, aal = 0
if (token.getPrincipal() instanceof final HangarPrincipal principal) {
this.tokenService.issueRefreshToken(principal.getUserId(), response);
principal.setAal(1); // todo check if email verified, if not, aal = 0
System.out.println("woooo 1");
return;
}
} else if (authentication instanceof final WebAuthnAuthenticationToken token) {
if (token.getPrincipal() instanceof final HangarUserPrincipal principal) {
if (token.getPrincipal() instanceof final HangarPrincipal principal) {
principal.setAal(2);
System.out.println("woooo");
System.out.println("woooo 2");
return;
}
}
@ -73,7 +79,7 @@ public class WebAuthnConfig {
})
.failureHandler(new AuthenticationEntryPointFailureHandler(this.entryPoint))
.attestationOptionsEndpoint()
.processingUrl("/api/internal/webauthn/attestation/options")
.processingUrl("/api/internal/auth/webauthn/attestation/options")
.attestation(AttestationConveyancePreference.NONE)
.timeout(60000L)
.rp()
@ -90,7 +96,7 @@ public class WebAuthnConfig {
.credProps(true)
.and()
.assertionOptionsEndpoint()
.processingUrl("/api/internal/webauthn/assertion/options")
.processingUrl("/api/internal/auth/webauthn/assertion/options")
.timeout(60000L)
.and().and()
.authenticationManager(authenticationManager);

View File

@ -53,8 +53,8 @@ public class TokenService extends HangarComponent {
return this.getVerifier().verify(token);
}
public void issueRefreshToken(final UserTable userTable, final HttpServletResponse response) {
final UserRefreshToken userRefreshToken = this.userRefreshTokenDAO.insert(new UserRefreshToken(userTable.getId(), UUID.randomUUID(), UUID.randomUUID()));
public void issueRefreshToken(final long userId, final HttpServletResponse response) {
final UserRefreshToken userRefreshToken = this.userRefreshTokenDAO.insert(new UserRefreshToken(userId, UUID.randomUUID(), UUID.randomUUID()));
this.addCookie(SecurityConfig.REFRESH_COOKIE_NAME, userRefreshToken.getToken().toString(), this.config.security.refreshTokenExpiry().toSeconds(), true, response);
}
@ -147,7 +147,7 @@ public class TokenService extends HangarComponent {
}
return new HangarApiPrincipal(userId, subject, locked, globalPermission, apiKeyTable);
} else {
return new HangarPrincipal(userId, subject, locked, globalPermission);
return new HangarPrincipal(userId, subject, locked, globalPermission, null);
}
}

View File

@ -59,7 +59,7 @@ public class FakeDataService extends HangarComponent {
try {
for (int udx = 0; udx < users; udx++) {
final UserTable user = this.createUser();
SecurityContextHolder.getContext().setAuthentication(HangarAuthenticationToken.createVerifiedToken(new HangarPrincipal(user.getUserId(), user.getName(), false, Permission.All), oldAuth.getCredentials()));
SecurityContextHolder.getContext().setAuthentication(HangarAuthenticationToken.createVerifiedToken(new HangarPrincipal(user.getUserId(), user.getName(), false, Permission.All, null), oldAuth.getCredentials()));
for (int pdx = 0; pdx < projectsPerUser; pdx++) {
this.createProject(user.getUserId());
}

View File

@ -2,7 +2,7 @@ package io.papermc.hangar.service.internal.auth;
import com.webauthn4j.data.PublicKeyCredentialUserEntity;
import com.webauthn4j.springframework.security.options.PublicKeyCredentialUserEntityProvider;
import io.papermc.hangar.security.authentication.user.HangarUserPrincipal;
import io.papermc.hangar.security.authentication.HangarPrincipal;
import java.math.BigInteger;
import org.springframework.security.core.Authentication;
@ -22,7 +22,7 @@ public class HangarPublicKeyCredentialUserEntityProvider implements PublicKeyCre
System.out.println("load public key " + authentication);
final String username = authentication.getName();
final HangarUserPrincipal principal = this.userDetailService.loadUserByUsername(username);
final HangarPrincipal principal = this.userDetailService.loadUserByUsername(username);
return new PublicKeyCredentialUserEntity(
BigInteger.valueOf(principal.getUserId()).toByteArray(), // TODO this isn't a good id I guess?
principal.getUsername(),

View File

@ -2,7 +2,7 @@ package io.papermc.hangar.service.internal.auth;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.security.authentication.user.HangarUserPrincipal;
import io.papermc.hangar.security.authentication.HangarPrincipal;
import io.papermc.hangar.service.internal.users.UserService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -21,7 +21,7 @@ public class HangarUserDetailService implements UserDetailsService {
}
@Override
public HangarUserPrincipal loadUserByUsername(final String username) throws UsernameNotFoundException {
public HangarPrincipal loadUserByUsername(final String username) throws UsernameNotFoundException {
if (username == null) {
throw new UsernameNotFoundException("no user with null username");
}
@ -31,6 +31,7 @@ public class HangarUserDetailService implements UserDetailsService {
throw new UsernameNotFoundException("no user in table");
}
// TODO store PW in db properly
return new HangarUserPrincipal(userTable, this.passwordEncoder.encode("admin123"), Permission.ViewPublicInfo);
// TODO load proper perms
return new HangarPrincipal(userTable.getUserId(), userTable.getName(), userTable.isLocked(), Permission.ViewPublicInfo, this.passwordEncoder.encode("admin123"));
}
}

View File

@ -35,6 +35,7 @@ public class HangarWebAuthnAuthenticatorService implements WebAuthnAuthenticator
@Override
public WebAuthnAuthenticator loadAuthenticatorByCredentialId(final byte[] credentialId) throws CredentialIdNotFoundException {
final WebAuthnAuthenticator auth = this.getByCredId(credentialId);
// TODO we need to make sure to load the proper hangar user principal here
System.out.println("loadAuthenticatorByCredentialId " + Arrays.toString(credentialId) + " " + auth.getUserPrincipal());
return auth;
}

View File

@ -52,7 +52,7 @@ export function encodeBase64Url(arrayBuffer: ArrayBuffer) {
}
export async function getAttestationOptions() {
const response = await useInternalApi<PublicKeyCredentialCreationOptions>("webauthn/attestation/options");
const response = await useInternalApi<PublicKeyCredentialCreationOptions>("auth/webauthn/attestation/options");
console.log("response", response);
// TODO error handling
@ -85,7 +85,7 @@ export async function getAttestationOptions() {
}
export async function getAssertionOptions() {
const response = await useInternalApi<PublicKeyCredentialRequestOptions>("webauthn/assertion/options");
const response = await useInternalApi<PublicKeyCredentialRequestOptions>("auth/webauthn/assertion/options");
console.log("response", response);
// TODO error handling

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useHead } from "@vueuse/head";
import { useRoute, useRouter } from "vue-router";
import { ref } from "vue";
import { computed, ref } from "vue";
import { useSeo } from "~/composables/useSeo";
import InputText from "~/lib/components/ui/InputText.vue";
import Button from "~/lib/components/design/Button.vue";
@ -9,11 +9,14 @@ import { useAxios } from "~/composables/useAxios";
import { useAuth } from "~/composables/useAuth";
import { useInternalApi } from "~/composables/useApi";
import { encodeBase64Url, getAssertionOptions } from "~/composables/useWebAuthN";
import { useAuthStore } from "~/store/auth";
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const aal = ref(2);
// todo hack for now, need to do this proper
const aal = computed(() => (authStore.authenticated ? 2 : 1));
// aal1
const username = ref("");
const password = ref("");

View File

@ -25,7 +25,7 @@ async function addAuthenticator() {
const publicKeyCredential = credential as PublicKeyCredential;
console.log("credential", credential);
const name = "DummyAuth";
await useInternalApi("webauthn/register", "POST", {
await useInternalApi("auth/webauthn/register", "POST", {
name,
credentialId: credential.id,
clientData: encodeBase64Url(publicKeyCredential.response.clientDataJSON),