@@ -297,6 +297,7 @@ public class ApiConstants { | |||
public static final String API_TAG_AUTH = "auth"; | |||
public static final String API_TAG_ACCOUNT = "account"; | |||
public static final String API_TAG_ACCOUNT_OBJECTS = "account-owned objects"; | |||
public static final String API_TAG_DEVICE = "device"; | |||
public static final String API_TAG_SEARCH = "search"; | |||
public static final String API_TAG_BACKUP_RESTORE = "backup and restore"; | |||
public static final String API_TAG_NODE = "node"; | |||
@@ -8,8 +8,9 @@ import lombok.AllArgsConstructor; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import org.cobbzilla.wizard.model.OpenApiSchema; | |||
@NoArgsConstructor @AllArgsConstructor | |||
@NoArgsConstructor @AllArgsConstructor @OpenApiSchema | |||
public class TrustedClientResponse { | |||
@Getter @Setter private String id; | |||
@@ -143,8 +143,8 @@ public class AuthResource { | |||
@GET @Path(EP_READY) | |||
@Operation(tags=API_TAG_UTILITY, | |||
summary="Determine if the API is running and ready for login", | |||
description="Determine if the API is running and ready for login", | |||
summary="Determine if Bubble is running and ready for login", | |||
description="Determine if Bubble is running and ready for login", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="empty response with status 200 if API is ready"), | |||
@ApiResponse(responseCode=SC_INVALID, description="error with status 422 if API is NOT ready") | |||
@@ -168,8 +168,8 @@ public class AuthResource { | |||
@GET @Path(EP_ACTIVATE) | |||
@Operation(tags=API_TAG_ACTIVATION, | |||
summary="Determine if the API has been activated", | |||
description="Determine if the API has been activated", | |||
summary="Determine if Bubble has been activated", | |||
description="Determine if Bubble has been activated", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="returns true if API is activated, false otherwise", | |||
content=@Content(mediaType=APPLICATION_JSON, examples={ | |||
@@ -1021,7 +1021,7 @@ public class AuthResource { | |||
@GET @Path(EP_SUPPORT+"/{locale}") | |||
@Operation(tags=API_TAG_UTILITY, | |||
summary="Get support information", | |||
summary="Get support information for a locale", | |||
description="Get support information for the given locale, if available. Use the default locale otherwise.", | |||
parameters=@Parameter(name="locale", description="locale to find support for"), | |||
responses=@ApiResponse(responseCode=SC_OK, description="SupportInfo object") | |||
@@ -1046,7 +1046,7 @@ public class AuthResource { | |||
@GET @Path(EP_APP_LINKS+"/{locale}") | |||
@Operation(tags=API_TAG_UTILITY, | |||
summary="Get links to native applications", | |||
summary="Get links to native applications for a locale", | |||
description="Get links to native applications for the given locale, if available. Use the default locale otherwise.", | |||
parameters=@Parameter(name="locale", description="locale to find app links for"), | |||
responses=@ApiResponse(responseCode=SC_OK, description="AppLinks object") | |||
@@ -126,8 +126,8 @@ public class MeResource { | |||
@GET @Path(EP_LOCALE) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_ACCOUNT, | |||
summary="Get the account locale", | |||
description="Get the account locale", | |||
summary="Get the locale for the current user", | |||
description="Get the locale for the current user", | |||
responses=@ApiResponse(responseCode=SC_OK, description="Locale string", content=@Content(mediaType=APPLICATION_JSON, examples=@ExampleObject(name="default locale", value="en_US"))) | |||
) | |||
public Response getLocale(@Context ContainerRequest ctx) { | |||
@@ -138,8 +138,8 @@ public class MeResource { | |||
@POST @Path(EP_LOCALE+"/{locale}") | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_ACCOUNT, | |||
summary="Set the account locale", | |||
description="Set the account locale", | |||
summary="Set the locale for the current user", | |||
description="Set the locale for the current user", | |||
responses=@ApiResponse(responseCode=SC_OK, description="updated Account object") | |||
) | |||
public Response setLocale(@Context ContainerRequest ctx, | |||
@@ -14,6 +14,8 @@ import bubble.model.account.message.ActionTarget; | |||
import bubble.model.device.Device; | |||
import bubble.service.account.StandardAuthenticatorService; | |||
import io.swagger.v3.oas.annotations.Operation; | |||
import io.swagger.v3.oas.annotations.Parameter; | |||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | |||
import io.swagger.v3.oas.annotations.security.SecurityRequirement; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -26,12 +28,13 @@ import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
import javax.ws.rs.core.Response; | |||
import static bubble.ApiConstants.EP_DELETE; | |||
import static bubble.ApiConstants.*; | |||
import static bubble.resources.account.AuthResource.newLoginSession; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | |||
import static org.cobbzilla.util.http.HttpStatusCodes.*; | |||
import static org.cobbzilla.wizard.cache.redis.RedisService.PX; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.SEC_API_KEY; | |||
@@ -54,7 +57,16 @@ public class TrustedAuthResource { | |||
@Getter(lazy=true) private final RedisService trustHashCache = redis.prefixNamespace("loginTrustedClient"); | |||
@PUT | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Establish trust with a device", | |||
description="Establish trust with a device. Returns a TrustedClientResponse with an id that can be used for future logins", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="TrustedClientResponse object"), | |||
@ApiResponse(responseCode=SC_NOT_FOUND, description="if the email/password login was incorrect"), | |||
@ApiResponse(responseCode=SC_INVALID, description="validation error. for example: TOTP required, or the device is already trusted") | |||
} | |||
) | |||
public Response trustClient(@Context ContainerRequest ctx, | |||
AccountLoginRequest request) { | |||
final Account caller = userPrincipal(ctx); | |||
@@ -78,6 +90,16 @@ public class TrustedAuthResource { | |||
} | |||
@POST | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Login as a trusted client", | |||
description="Login as a trusted client. Starts a new API session", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="Account object with session token"), | |||
@ApiResponse(responseCode=SC_NOT_FOUND, description="if the email/password login was incorrect"), | |||
@ApiResponse(responseCode=SC_INVALID, description="validation error. for example: TOTP required, or the device is already trusted") | |||
} | |||
) | |||
public Response loginTrustedClient(@Context ContainerRequest ctx, | |||
@Valid TrustedClientLoginRequest request) { | |||
final Account account = validateTrustedCall(request); | |||
@@ -92,7 +114,17 @@ public class TrustedAuthResource { | |||
} | |||
@DELETE @Path(EP_DELETE+"/{device}") | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Remove trust from a trusted client", | |||
description="Remove trust from a trusted client", | |||
parameters=@Parameter(name="device", description="uuid of the device"), | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="Account object with session token"), | |||
@ApiResponse(responseCode=SC_NOT_FOUND, description="if the email/password login was incorrect"), | |||
@ApiResponse(responseCode=SC_INVALID, description="validation error. for example: TOTP required, or the device is already trusted") | |||
} | |||
) | |||
public Response removeTrustedClient(@Context ContainerRequest ctx, | |||
@PathParam("device") String deviceId) { | |||
final Account caller = userPrincipal(ctx); | |||
@@ -7,6 +7,7 @@ package bubble.resources.account; | |||
import bubble.model.account.Account; | |||
import bubble.model.device.Device; | |||
import io.swagger.v3.oas.annotations.Operation; | |||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | |||
import io.swagger.v3.oas.annotations.security.SecurityRequirement; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.io.FileUtil; | |||
@@ -23,7 +24,9 @@ import javax.ws.rs.core.Response; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import static bubble.ApiConstants.API_TAG_DEVICE; | |||
import static org.cobbzilla.util.http.HttpContentTypes.*; | |||
import static org.cobbzilla.util.http.HttpStatusCodes.*; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.SEC_API_KEY; | |||
@@ -58,7 +61,15 @@ public class VpnConfigResource { | |||
@GET @Path("/QR.png") | |||
@Produces(IMAGE_PNG) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Get QR code PNG image for device", | |||
description="Get QR code PNG image for device", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="QR code PNG image data"), | |||
@ApiResponse(responseCode=SC_FORBIDDEN, description="if caller is not admin or does not own the device") | |||
} | |||
) | |||
public Response qrCode(@Context ContainerRequest ctx) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin() && !caller.getUuid().equals(device.getAccount())) return forbidden(); | |||
@@ -67,7 +78,15 @@ public class VpnConfigResource { | |||
@GET @Path("/QR.png.base64") | |||
@Produces(TEXT_PLAIN) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Get QR code PNG image, as Base64-encoded string", | |||
description="Get QR code PNG image, as Base64-encoded string", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="Base64-encoded QR code PNG image data"), | |||
@ApiResponse(responseCode=SC_FORBIDDEN, description="if caller is not admin or does not own the device") | |||
} | |||
) | |||
public Response qrCodeBase64(@Context ContainerRequest ctx) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin() && !caller.getUuid().equals(device.getAccount())) return forbidden(); | |||
@@ -82,7 +101,15 @@ public class VpnConfigResource { | |||
@GET @Path("/vpn.conf") | |||
@Produces(APPLICATION_OCTET_STREAM) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Get WireGuard vpn.conf file for device", | |||
description="Get WireGuard vpn.conf file for device", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="vpn.conf file data"), | |||
@ApiResponse(responseCode=SC_FORBIDDEN, description="if caller is not admin or does not own the device") | |||
} | |||
) | |||
public Response confFile(@Context ContainerRequest ctx) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin() && !caller.getUuid().equals(device.getAccount())) return forbidden(); | |||
@@ -95,7 +122,15 @@ public class VpnConfigResource { | |||
@GET @Path("/vpn.conf.base64") | |||
@Produces(TEXT_PLAIN) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY)) | |||
@Operation(security=@SecurityRequirement(name=SEC_API_KEY), | |||
tags=API_TAG_DEVICE, | |||
summary="Get WireGuard vpn.conf file, as Base64-encoded string", | |||
description="Get WireGuard vpn.conf file for device, as Base64-encoded string", | |||
responses={ | |||
@ApiResponse(responseCode=SC_OK, description="Base64-encoded vpn.conf file data"), | |||
@ApiResponse(responseCode=SC_FORBIDDEN, description="if caller is not admin or does not own the device") | |||
} | |||
) | |||
public Response confFileBase64(@Context ContainerRequest ctx) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin() && !caller.getUuid().equals(device.getAccount())) return forbidden(); | |||