Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
MaKleSoft committed Jun 29, 2023
1 parent 81078d3 commit 48304ff
Show file tree
Hide file tree
Showing 13 changed files with 537 additions and 612 deletions.
3 changes: 2 additions & 1 deletion packages/admin/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { css, customElement, html, LitElement, property, query, state } from "@p
import { ServiceWorker } from "@padloc/app/src/mixins/service-worker";
import { StateMixin } from "@padloc/app/src/mixins/state";
import { Routing } from "@padloc/app/src/mixins/routing";
import { ErrorHandling } from "@padloc/app/src/mixins/error-handling";
import { mixins, shared } from "@padloc/app/src/styles";
import "@padloc/app/src/elements/button";
import "@padloc/app/src/elements/icon";
Expand All @@ -16,7 +17,7 @@ import "./orgs";
import "./config";

@customElement("pl-admin-app")
export class App extends ServiceWorker(StateMixin(Routing(LitElement))) {
export class App extends ServiceWorker(ErrorHandling(StateMixin(Routing(LitElement)))) {
@property({ attribute: false })
readonly routePattern = /^([^\/]*)(?:\/([^\/]+))?/;

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/elements/login-signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ export class LoginOrSignup extends StartForm {
this._confirmPasswordButton.start();

try {
await this.app.signup({ email, password, name, authToken: this._authToken });
await this.app.signup({ email, password, name, authToken: this._authToken, asAdmin: this.asAdmin });
this._confirmPasswordButton.success();
const { email: _email, name: _name, authToken, deviceTrusted, ...params } = this.router.params;
this.go("signup/success", params);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,12 +656,14 @@ export class App {
authToken,
/** Information about the [[Invite]] object if signup was initiated through invite link */
invite,
asAdmin,
}: {
email: string;
password: string;
name: string;
authToken: string;
invite?: { id: string; org: string };
asAdmin?: boolean;
}) {
// Inialize account object
const account = new Account();
Expand Down Expand Up @@ -689,7 +691,7 @@ export class App {
);

// Sign into new account
await this.login({ email, password });
await this.login({ email, password, asAdmin });
}

/**
Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ParamDefinition {
readonly options?: string[];
readonly default?: any;
readonly description?: string;
readonly envVars?: string[];
}

export function ConfigParam(
Expand All @@ -22,11 +23,13 @@ export function ConfigParam(
options,
required = false,
default: def,
envVars,
}: {
secret?: boolean;
options?: string[];
required?: boolean | { prop: string; value: string | string[] };
default?: any;
envVars?: string[];
} = {},
description?: string
) {
Expand All @@ -42,6 +45,7 @@ export function ConfigParam(
required,
default: def,
description,
envVars,
});
};
}
Expand All @@ -59,7 +63,7 @@ export class Config extends Serializable {
outputSecrets = false;

fromEnv(env: { [prop: string]: string }, prefix = "PL_") {
for (const { prop, type } of this._paramDefinitions || []) {
for (const { prop, type, envVars } of this._paramDefinitions || []) {
// type is another config object
if (typeof type === "function") {
const newPrefix = `${prefix}${prop.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}_`;
Expand All @@ -79,7 +83,13 @@ export class Config extends Serializable {

const varName = `${prefix}${prop.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}`;

let str = env[varName];
let str: string | undefined = undefined;

const vars = [varName, ...(envVars || [])];

while (typeof str === "undefined" && vars.length) {
str = env[vars.pop() || ""];
}

if (typeof str === "undefined") {
continue;
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/config/padloc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,36 @@ export class RequestLogConfig extends RequestLoggerConfig {
storage?: DataStorageConfig;
}

export class PWAConfig extends Config {
@ConfigParam("string", { required: true, default: "./pwa" }, "The build output directory directory.")
dir: string = "./pwa";

@ConfigParam(
"string",
{ required: true, default: "http://localhost:8080", envVars: ["PL_CLIENT_URL"] },
"The url the app will be available on."
)
url: string = "http://localhost:8080";

@ConfigParam(
"number",
{ default: 8080 },
"The port to serve the app over. Omit this if you want to " +
"serve those files another way (like through nginx or caddy)."
)
port?: number;

@ConfigParam("boolean", { default: false }, "Set to `true` to disable adding a CSP to the app.")
disableCSP?: boolean = false;
}

export class AdminConfig extends PWAConfig {}

export class AssetsConfig extends Config {
@ConfigParam("string", { required: true, default: "./assets" }, "The directory containing the assets.")
dir: string = "./assets";
}

export class PadlocConfig extends Config {
constructor(init: Partial<PadlocConfig> = {}) {
super();
Expand Down Expand Up @@ -212,6 +242,19 @@ export class PadlocConfig extends Config {
)
provisioning = new ProvisioningConfig();

@ConfigParam(PWAConfig, { required: true }, "The configuration for the web app.")
pwa: PWAConfig = new PWAConfig();

@ConfigParam(AdminConfig, { required: true }, "The configuration for the admin portal.")
admin: AdminConfig = new AdminConfig();

@ConfigParam(
AssetsConfig,
{ required: true },
"Config for assets like app icon, logos, email templates, custom style sheets etc."
)
assets: AssetsConfig = new AssetsConfig();

@ConfigParam(
DirectoryConfig,
{ required: { prop: "provisioning.backend", value: "directory" } },
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { Config, ConfigParam } from "../config";

/** Server configuration */
export class ServerConfig extends Config {
/** URL where the client interface is hosted. Used for creating links into the application */
@ConfigParam(
"string",
{ required: true, default: "http://localhost:8080" },
"URL where the client interface is hosted. Used for creating links into the application"
{ required: true, default: "http://localhost:3000" },
"The url the server will be reachable on."
)
clientUrl = "http://localhost:8080";
url: string = "http://localhost:3000";

/** Email address to report critical errors to */
@ConfigParam("string", {}, "Email address for reporting unexpected server errors.")
Expand Down
104 changes: 83 additions & 21 deletions packages/core/src/encoding.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Err, ErrorCode } from "./error";
import { toByteArray, fromByteArray, byteLength, isBase64 } from "./base64";
import { upgrade, downgrade } from "./migrations";
import { upgrade, downgrade, LATEST_VERSION } from "./migrations";
import { BigInteger } from "../vendor/jsbn";

export { bytesToBase32, base32ToBytes } from "./base32";
Expand All @@ -10,10 +10,51 @@ export interface SerializationOptions {
toProperty: string;
exclude: boolean;
arrayDeserializeIndividually: boolean;
type: "string" | "number" | "boolean" | SerializableConstructor | SerializableConstructor[];
options?: any[];
default?: any;
description?: string;
fromRaw: (raw: any) => any;
toRaw: (val: any, version?: string) => any;
}

function schemaFromPropertyDefinition(def: SerializationOptions) {
let schema: any = {};

switch (def.type) {
case "string":
schema = def.options
? { type: "string", enum: def.options, default: def.default }
: { type: "string", default: def.default };
break;
case "boolean":
schema = { type: "boolean", default: def.default };
break;
case "number":
schema = { type: "number", default: def.default };
break;
default:
schema = Array.isArray(def.type)
? {
type: "object",
oneOf: def.type.map((t) => new t().getSchema()),
}
: new def.type().getSchema();
}

if (def.description) {
schema.description = def.description;
}

if (def.options) {
schema.description = `${schema.description || ""}\n\nPossible values are:${def.options.map(
(option) => ` \`${option}\``
)}`;
}

return schema;
}

function registerSerializationOptions(proto: Serializable, property: string, opts: Partial<SerializationOptions>) {
if (!proto.hasOwnProperty("_propertySerializationOptions")) {
const parentOptions = proto._propertySerializationOptions || [];
Expand All @@ -22,19 +63,16 @@ function registerSerializationOptions(proto: Serializable, property: string, opt

// proto._propertySerializationOptions = proto._propertySerializationOptions.filter(o => o.property === property);

proto._propertySerializationOptions.unshift(
Object.assign(
{
property,
toProperty: property,
exclude: false,
arrayDeserializeIndividually: true,
toRaw: () => {},
fromRaw: () => {},
},
opts
)
);
proto._propertySerializationOptions!.unshift({
property,
toProperty: property,
exclude: false,
arrayDeserializeIndividually: true,
toRaw: () => {},
fromRaw: () => {},
type: "string",
...opts,
});
}

/**
Expand All @@ -46,6 +84,7 @@ export function AsBigInteger(toProperty?: string) {
toProperty: toProperty || prop,
toRaw: (val: BigInteger) => val.toString(),
fromRaw: (raw: string) => new BigInteger(raw),
type: "string",
});
}

Expand All @@ -61,6 +100,7 @@ export function AsSerializable(cls: SerializableConstructor | SerializableConstr
const c = Array.isArray(cls) ? cls.find((c) => new c().kind === raw.kind) : cls;
return c ? new c().fromRaw(raw) : raw;
},
type: cls,
});
}

Expand All @@ -70,6 +110,7 @@ export function AsBytes(toProperty?: string) {
toProperty: toProperty || prop,
toRaw: (val: any) => bytesToBase64(val),
fromRaw: (raw: any) => base64ToBytes(raw),
type: "string",
});
}

Expand Down Expand Up @@ -163,7 +204,7 @@ export class Serializable {
return this.constructor.name.toLowerCase();
}

_propertySerializationOptions!: SerializationOptions[];
_propertySerializationOptions?: SerializationOptions[];

/**
* This is called during deserialization and should verify that all
Expand Down Expand Up @@ -249,6 +290,31 @@ export class Serializable {
return new this.constructor().fromRaw(this.toRaw());
}

getSchema(): any {
const schema: any = {
type: "object",
properties: {
kind: { const: this.kind },
version: { const: LATEST_VERSION },
},
additionalProperties: false,
};

for (const [prop, val] of Object.entries(this)) {
const def = this._propertySerializationOptions?.find((opts) => opts.property === prop);
if (prop.startsWith("_") || (def && def.exclude)) {
continue;
}
if (def) {
schema.properties[prop] = schemaFromPropertyDefinition(def);
} else {
schema.properties[prop] = val instanceof Serializable ? val.getSchema() : typeof val;
}
}

return schema;
}

/**
* Transform this object into a raw javascript object used for
* serialization. The default implementation simply copies all iterable
Expand All @@ -261,9 +327,7 @@ export class Serializable {
let raw = {} as any;

for (const [prop, val] of Object.entries(this)) {
const opts =
this._propertySerializationOptions &&
this._propertySerializationOptions.find((opts) => opts.property === prop);
const opts = this._propertySerializationOptions?.find((opts) => opts.property === prop);

if (prop.startsWith("_") || (opts && opts.exclude)) {
continue;
Expand Down Expand Up @@ -292,9 +356,7 @@ export class Serializable {
continue;
}

const opts =
this._propertySerializationOptions &&
this._propertySerializationOptions.find((opts) => opts.toProperty === prop);
const opts = this._propertySerializationOptions?.find((opts) => opts.toProperty === prop);

// Skip properties that have no serialization options associated with them
// and are not explicitly defined as a property on the class
Expand Down
11 changes: 5 additions & 6 deletions packages/core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ export class Controller extends API {
authenticatorId: authenticator.id,
type: authenticator.type,
purpose:
purpose === AuthPurpose.Login && auth.accountStatus !== AccountStatus.Active
[AuthPurpose.Login, AuthPurpose.AdminLogin].includes(purpose) &&
auth.accountStatus !== AccountStatus.Active
? AuthPurpose.Signup
: purpose,
device: this.context.device,
Expand Down Expand Up @@ -1241,7 +1242,7 @@ export class Controller extends API {
orgName: invite.org.name,
invitedBy: invite.invitedBy!.name || invite.invitedBy!.email,
acceptInviteUrl: `${removeTrailingSlash(
this.config.server.clientUrl
this.config.pwa.url
)}${path}?${params.toString()}`,
})
);
Expand Down Expand Up @@ -1309,7 +1310,7 @@ export class Controller extends API {
member.email,
new JoinOrgInviteCompletedMessage({
orgName: org.name,
openAppUrl: `${removeTrailingSlash(this.config.server.clientUrl)}/org/${org.id}`,
openAppUrl: `${removeTrailingSlash(this.config.pwa.url)}/org/${org.id}`,
})
);
} catch (e) {}
Expand Down Expand Up @@ -1608,9 +1609,7 @@ export class Controller extends API {
new JoinOrgInviteAcceptedMessage({
orgName: org.name,
invitee: invite.invitee.name || invite.invitee.email,
confirmMemberUrl: `${removeTrailingSlash(this.config.server.clientUrl)}/invite/${org.id}/${
invite.id
}`,
confirmMemberUrl: `${removeTrailingSlash(this.config.pwa.url)}/invite/${org.id}/${invite.id}`,
})
);
} catch (e) {}
Expand Down

0 comments on commit 48304ff

Please sign in to comment.