This commit is contained in:
Rory Powell 2023-07-14 16:55:48 +01:00
parent 1964148581
commit c8fc67d230
18 changed files with 88 additions and 55 deletions

View File

@ -29,7 +29,7 @@ export const account = (partial: Partial<Account> = {}): Account => {
size: "10+",
profession: "Software Engineer",
quotaUsage: quotas.usage(),
...partial
...partial,
}
}

View File

@ -1 +1 @@
export * from "./platform"
export * from "./platform"

View File

@ -1 +1 @@
export * as installation from "./installation"
export * as installation from "./installation"

View File

@ -7,6 +7,6 @@ export function install(): Installation {
_id: "install",
_rev: db.rev(),
installId: generator.guid(),
version: generator.string()
version: generator.string(),
}
}
}

View File

@ -138,9 +138,7 @@ interface GenerateLicenseOpts {
billing?: Billing
}
export const license = (
opts: GenerateLicenseOpts = {}
): License => {
export const license = (opts: GenerateLicenseOpts = {}): License => {
return {
features: opts.features || [],
quotas: opts.quotas || quotas(),
@ -149,23 +147,21 @@ export const license = (
}
}
export function offlineLicense (
opts: GenerateLicenseOpts = {}
): OfflineLicense {
export function offlineLicense(opts: GenerateLicenseOpts = {}): OfflineLicense {
const base = license(opts)
return {
...base,
expireAt: new Date().toISOString(),
identifier: offlineIdentifier()
identifier: offlineIdentifier(),
}
}
export function offlineIdentifier(
installId: string = generator.guid(),
tenantId: string = generator.guid(),
tenantId: string = generator.guid()
): OfflineIdentifier {
return {
installId,
tenantId
tenantId,
}
}
}

View File

@ -11,7 +11,7 @@
ButtonGroup,
notifications,
CopyInput,
File
File,
} from "@budibase/bbui"
import { auth, admin } from "stores/portal"
import { redirect } from "@roxi/routify"
@ -35,9 +35,7 @@
let offlineLicenseIdentifier = ""
let offlineLicense = undefined
const offlineLicenseExtensions = [
".txt",
]
const offlineLicenseExtensions = [".txt"]
// Make sure page can't be visited directly in cloud
$: {
@ -97,7 +95,7 @@
const license = await API.getOfflineLicense()
if (license) {
offlineLicense = {
name: "license"
name: "license",
}
} else {
offlineLicense = undefined
@ -147,7 +145,7 @@
// prevent file preview jitter by assigning constant
// as soon as possible
offlineLicense = {
name: "license"
name: "license",
}
const reader = new FileReader()
reader.readAsText(event.detail)
@ -202,7 +200,9 @@
{#if $admin.offlineMode}
<Layout gap="XS" noPadding>
<Heading size="XS">Installation identifier</Heading>
<Body size="S">Share this with support@budibase.com to obtain your offline license</Body>
<Body size="S"
>Share this with support@budibase.com to obtain your offline license</Body
>
</Layout>
<Layout noPadding>
<div class="fields">
@ -263,8 +263,12 @@
<Layout noPadding gap="S">
<Body size="S">You are currently on the {license.plan.type} plan</Body>
<div>
<Body size="S">If you purchase or update your plan on the account</Body>
<Body size="S">portal, click the refresh button to sync those changes</Body>
<Body size="S"
>If you purchase or update your plan on the account</Body
>
<Body size="S"
>portal, click the refresh button to sync those changes</Body
>
</div>
<Body size="XS">
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {

View File

@ -17,7 +17,7 @@ export const DEFAULT_CONFIG = {
adminUser: { checked: false },
sso: { checked: false },
},
offlineMode: false
offlineMode: false,
}
export function createAdminStore() {

View File

@ -1,5 +1,4 @@
export const buildLicensingEndpoints = API => ({
// LICENSE KEY
activateLicenseKey: async data => {
@ -31,7 +30,7 @@ export const buildLicensingEndpoints = API => ({
return API.post({
url: "/api/global/license/offline",
body: {
offlineLicenseToken
offlineLicenseToken,
},
})
},

@ -1 +1 @@
Subproject commit 1a5207d91fb9e0835562c708dd9c421973026543
Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6

View File

@ -36,4 +36,4 @@ export interface CreateOfflineLicenseRequest {
export interface GetOfflineLicenseResponse {
offlineLicenseToken: string
license: OfflineLicense
}
}

View File

@ -5,7 +5,7 @@ export interface ActivateLicenseKeyRequest {
}
export interface GetLicenseKeyResponse {
licenseKey: string,
licenseKey: string
}
// OFFLINE LICENSE

View File

@ -2,7 +2,7 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "."
import { ISO8601 } from "../../shared"
export interface OfflineIdentifier {
installId: string,
installId: string
tenantId: string
}

View File

@ -2,4 +2,4 @@ export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
export type ISO8601 = string
export type ISO8601 = string

View File

@ -11,16 +11,16 @@ const pro = {
activateOfflineLicense: jest.fn(),
getOfflineLicenseToken: jest.fn(),
deleteOfflineLicenseToken: jest.fn(),
getIdentifierBase64: jest.fn()
getIdentifierBase64: jest.fn(),
},
cache: {
...actual.licensing.cache,
refresh: jest.fn(),
}
},
},
quotas: {
...actual.quotas,
getQuotaUsage: jest.fn()
getQuotaUsage: jest.fn(),
},
}

View File

@ -10,7 +10,9 @@ import {
// LICENSE KEY
export async function activateLicenseKey(ctx: UserCtx<ActivateLicenseKeyRequest>) {
export async function activateLicenseKey(
ctx: UserCtx<ActivateLicenseKeyRequest>
) {
const { licenseKey } = ctx.request.body
await licensing.keys.activateLicenseKey(licenseKey)
ctx.status = 200
@ -33,13 +35,17 @@ export async function deleteLicenseKey(ctx: UserCtx<void, void>) {
// OFFLINE LICENSE
export async function activateOfflineLicenseToken(ctx: UserCtx<ActivateOfflineLicenseTokenRequest>) {
export async function activateOfflineLicenseToken(
ctx: UserCtx<ActivateOfflineLicenseTokenRequest>
) {
const { offlineLicenseToken } = ctx.request.body
await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken)
ctx.status = 200
}
export async function getOfflineLicenseToken(ctx: UserCtx<void, GetOfflineLicenseTokenResponse>) {
export async function getOfflineLicenseToken(
ctx: UserCtx<void, GetOfflineLicenseTokenResponse>
) {
const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken()
if (offlineLicenseToken) {
ctx.body = { offlineLicenseToken: "*" }
@ -54,7 +60,9 @@ export async function deleteOfflineLicenseToken(ctx: UserCtx<void, void>) {
ctx.status = 204
}
export async function getOfflineLicenseIdentifier(ctx: UserCtx<void, GetOfflineIdentifierResponse>) {
export async function getOfflineLicenseIdentifier(
ctx: UserCtx<void, GetOfflineIdentifierResponse>
) {
const identifierBase64 = await licensing.offline.getIdentifierBase64()
ctx.body = { identifierBase64 }
ctx.status = 200

View File

@ -3,13 +3,17 @@ import * as controller from "../../controllers/global/license"
import { middleware } from "@budibase/backend-core"
import Joi from "joi"
const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({
const activateLicenseKeyValidator = middleware.joiValidator.body(
Joi.object({
licenseKey: Joi.string().required(),
}).required())
}).required()
)
const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({
const activateOfflineLicenseValidator = middleware.joiValidator.body(
Joi.object({
offlineLicenseToken: Joi.string().required(),
}).required())
}).required()
)
const router: Router = new Router()
@ -17,13 +21,24 @@ router
.post("/api/global/license/refresh", controller.refresh)
.get("/api/global/license/usage", controller.getQuotaUsage)
// LICENSE KEY
.post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey)
.post(
"/api/global/license/key",
activateLicenseKeyValidator,
controller.activateLicenseKey
)
.get("/api/global/license/key", controller.getLicenseKey)
.delete("/api/global/license/key", controller.deleteLicenseKey)
// OFFLINE LICENSE
.post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken)
.post(
"/api/global/license/offline",
activateOfflineLicenseValidator,
controller.activateOfflineLicenseToken
)
.get("/api/global/license/offline", controller.getOfflineLicenseToken)
.delete("/api/global/license/offline", controller.deleteOfflineLicenseToken)
.get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier)
.get(
"/api/global/license/offline/identifier",
controller.getOfflineLicenseIdentifier
)
export default router

View File

@ -37,7 +37,9 @@ describe("/api/global/license", () => {
describe("POST /api/global/license/key", () => {
it("returns 200", async () => {
const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" })
const res = await config.api.license.activateLicenseKey({
licenseKey: "licenseKey",
})
expect(res.status).toBe(200)
expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey")
})
@ -53,7 +55,7 @@ describe("/api/global/license", () => {
const res = await config.api.license.getLicenseKey()
expect(res.status).toBe(200)
expect(res.body).toEqual({
licenseKey: "*"
licenseKey: "*",
})
})
})
@ -68,8 +70,12 @@ describe("/api/global/license", () => {
describe("POST /api/global/license/offline", () => {
it("activates offline license", async () => {
const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"})
expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken")
const res = await config.api.license.activateOfflineLicense({
offlineLicenseToken: "offlineLicenseToken",
})
expect(licensing.offline.activateOfflineLicenseToken).toBeCalledWith(
"offlineLicenseToken"
)
expect(res.status).toBe(200)
})
})
@ -80,11 +86,13 @@ describe("/api/global/license", () => {
expect(res.status).toBe(404)
})
it("returns 200 + offline license token", async () => {
licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken")
licensing.offline.getOfflineLicenseToken.mockResolvedValue(
"offlineLicenseToken"
)
const res = await config.api.license.getOfflineLicense()
expect(res.status).toBe(200)
expect(res.body).toEqual({
offlineLicenseToken: "*"
offlineLicenseToken: "*",
})
})
})
@ -103,7 +111,7 @@ describe("/api/global/license", () => {
const res = await config.api.license.getOfflineLicenseIdentifier()
expect(res.status).toBe(200)
expect(res.body).toEqual({
identifierBase64: "base64"
identifierBase64: "base64",
})
})
})

View File

@ -1,6 +1,9 @@
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types"
import {
ActivateLicenseKeyRequest,
ActivateOfflineLicenseTokenRequest,
} from "@budibase/types"
export class LicenseAPI extends TestAPI {
constructor(config: TestConfiguration) {
@ -35,7 +38,7 @@ export class LicenseAPI extends TestAPI {
.delete("/api/global/license/key")
.set(this.config.defaultHeaders())
}
activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => {
activateOfflineLicense = async (body: ActivateOfflineLicenseTokenRequest) => {
return this.request
.post("/api/global/license/offline")
.send(body)