14 Commits

Author SHA1 Message Date
philkunz 0d613fd634 1.4.1
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-07 15:14:45 +02:00
philkunz a94d1875bd fix(core): Bug fixes and UI enhancements 2024-10-07 15:14:44 +02:00
philkunz 46844fed58 1.4.0
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-07 10:26:21 +02:00
philkunz 03a8536297 feat(core): Refactored plugin and request handling to use idpInterfaces 2024-10-07 10:26:21 +02:00
philkunz 1bfdc67a0e 1.3.1
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-07 00:08:53 +02:00
philkunz 3cb79c8dbe fix(account): Fix: updated cleanupViews method to correctly iterate over children. 2024-10-07 00:08:52 +02:00
philkunz c547105ab6 1.3.0
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-06 23:56:04 +02:00
philkunz f7600ca83f feat(account): Implement account and organization management features 2024-10-06 23:56:03 +02:00
philkunz 2c0e771da2 1.2.2
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-04 15:43:37 +02:00
philkunz 4deaafc3a2 fix(core): Update dependencies and refactor registration process 2024-10-04 15:43:36 +02:00
philkunz 629bf19845 1.2.1
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-04 02:18:48 +02:00
philkunz 9e2d45123f fix(core): Added logging for user email login process and fixed client URL parsing 2024-10-04 02:18:47 +02:00
philkunz 833b5e0a84 1.2.0
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2024-10-01 20:03:50 +02:00
philkunz e36b701812 feat(web): Improve UI styling and add registration prompt 2024-10-01 20:03:49 +02:00
49 changed files with 3167 additions and 915 deletions
+50
View File
@@ -1,5 +1,55 @@
# Changelog
## 2024-10-07 - 1.4.1 - fix(core)
Bug fixes and UI enhancements
- Updated packages to resolve compatibility issues.
- Optimized the transition animations for the center container.
- Improved the initialization logic for navigating between views.
- Enhanced UI with better organization selection handling.
## 2024-10-07 - 1.4.0 - feat(core)
Refactored plugin and request handling to use 'idpInterfaces'
- Switched from using 'lointReception' to 'idpInterfaces' in various TypeScript sources.
- Updated references to request and data interfaces across multiple modules.
- Improved account handling with new navigation options.
## 2024-10-07 - 1.3.1 - fix(account)
Fix: updated cleanupViews method to correctly iterate over children.
- Fixed the iteration over view container children by converting it to an array before removing children. This resolves potential errors due to incorrect for-loop execution on HTMLCollection.
## 2024-10-06 - 1.3.0 - feat(account)
Implement account and organization management features
- Added account management UI with organization selection
- Introduced organization creation and selection functionalities
- Implemented subscription view with Paddle setup integration
## 2024-10-04 - 1.2.2 - fix(core)
Update dependencies and refactor registration process
- Updated @design.estate/dees-catalog, @design.estate/dees-domtools, and @design.estate/dees-element dependencies to their latest versions.
- Refactored registration process to improve validation flow.
- Improved user interface for login and registration prompts.
- Fixed issues with email and token validation during registration.
## 2024-10-04 - 1.2.1 - fix(core)
Added logging for user email login process and fixed client URL parsing
- Added info logging when loginWithEmail is requested and when a user is found.
- Ensured reception client parses the URL correctly in IdpClient and IdpRequests classes.
- Updated login process flow in idp-logincontainer and idp-loginprompt elements.
- Improved element loading mechanism with updated state management in viewcontainer.
## 2024-10-01 - 1.2.0 - feat(web)
Improve UI styling and add registration prompt
- Updated max-width of login container to improve layout consistency
- Added new component for user registration
- Improved styling for various elements including buttons and text boxes
## 2024-10-01 - 1.1.1 - fix(core)
Corrected typos and added missing keywords.
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@idp.global/idp.global",
"version": "1.1.1",
"version": "1.4.1",
"description": "An identity provider software managing user authentications, registrations, and sessions.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
@@ -21,9 +21,9 @@
"@api.global/typedserver": "^3.0.51",
"@api.global/typedsocket": "^3.0.1",
"@consentsoftware_private/catalog": "^1.0.73",
"@design.estate/dees-catalog": "^1.1.8",
"@design.estate/dees-domtools": "^2.0.23",
"@design.estate/dees-element": "^2.0.15",
"@design.estate/dees-catalog": "^1.2.0",
"@design.estate/dees-domtools": "^2.0.64",
"@design.estate/dees-element": "^2.0.39",
"@push.rocks/lik": "^6.0.15",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/smartdata": "^5.2.10",
@@ -36,15 +36,15 @@
"@push.rocks/smartpath": "^5.0.5",
"@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstate": "^2.0.0",
"@push.rocks/smartstate": "^2.0.19",
"@push.rocks/smarttime": "^4.0.8",
"@push.rocks/smartunique": "^3.0.9",
"@push.rocks/smarturl": "^3.0.7",
"@push.rocks/smarturl": "^3.1.0",
"@push.rocks/taskbuffer": "^3.1.7",
"@push.rocks/webjwt": "^1.0.9",
"@push.rocks/websetup": "^3.0.15",
"@push.rocks/webstore": "^2.0.20",
"@serve.zone/platformclient": "^1.0.6",
"@serve.zone/platformclient": "^1.1.2",
"@tsclass/tsclass": "^4.1.2",
"@uptime.link/webwidget": "^1.1.2"
},
@@ -54,7 +54,7 @@
"@git.zone/tsrun": "^1.2.8",
"@git.zone/tswatch": "^2.0.1",
"@push.rocks/projectinfo": "^5.0.1",
"@types/node": "^22.7.2"
"@types/node": "^22.7.4"
},
"private": true,
"repository": {
+1239 -135
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@idp.global/idp.global',
version: '1.1.1',
version: '1.4.1',
description: 'An identity provider software managing user authentications, registrations, and sessions.'
}
+1
View File
@@ -20,6 +20,7 @@ export const runCli = async () => {
mongoDbUrl: await serviceQenv.getEnvVarOnDemand('MONGO_DB_URL'),
},
websiteServer: websiteServer,
baseUrl: await serviceQenv.getEnvVarOnDemand('IDP_BASEURL'),
});
await reception.start();
+2 -2
View File
@@ -3,8 +3,8 @@ import * as path from 'path';
export { path };
// Project scope
import * as lointReception from '../dist_ts_interfaces/index.js';
export { lointReception };
import * as idpInterfaces from '../dist_ts_interfaces/index.js';
export { idpInterfaces };
// @api.global scope
import * as typedserver from '@api.global/typedserver';
+2 -2
View File
@@ -8,7 +8,7 @@ import { User } from './classes.user.js';
@plugins.smartdata.Manager()
export class BillingPlan extends plugins.smartdata.SmartDataDbDoc<
BillingPlan,
plugins.lointReception.data.IBillingPlan,
plugins.idpInterfaces.data.IBillingPlan,
BillingPlanManager
> {
// STATIC
@@ -20,7 +20,7 @@ export class BillingPlan extends plugins.smartdata.SmartDataDbDoc<
public id: string;
@plugins.smartdata.svDb()
public data: plugins.lointReception.data.IBillingPlan['data'] = {
public data: plugins.idpInterfaces.data.IBillingPlan['data'] = {
type: null,
organizationId: null,
lastProcessed: null,
+1 -1
View File
@@ -14,7 +14,7 @@ export class BillingPlanManager {
constructor(receptionRefArg: Reception) {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_UpdatePaymentMethod>('updatePaymentMethod', async reqDataArg => {
this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_UpdatePaymentMethod>('updatePaymentMethod', async reqDataArg => {
const user = await this.receptionRef.userManager.getUserByJwt(reqDataArg.jwtString);
const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({
id: reqDataArg.orgId,
+3 -3
View File
@@ -6,7 +6,7 @@ import { JwtManager } from './classes.jwtmanager.js';
* Both need to be unique and both can be changed.
*/
@plugins.smartdata.Manager()
export class Jwt extends plugins.smartdata.SmartDataDbDoc<Jwt, plugins.lointReception.data.IJwt, JwtManager> {
export class Jwt extends plugins.smartdata.SmartDataDbDoc<Jwt, plugins.idpInterfaces.data.IJwt, JwtManager> {
// STATIC
public static async createJwtForRefreshToken(
jwtManagerInstance: JwtManager,
@@ -48,7 +48,7 @@ export class Jwt extends plugins.smartdata.SmartDataDbDoc<Jwt, plugins.lointRece
id: jwt.id,
blocked: null,
data: jwt.data,
} as plugins.lointReception.data.IJwt);
} as plugins.idpInterfaces.data.IJwt);
return jwtString;
}
@@ -60,7 +60,7 @@ export class Jwt extends plugins.smartdata.SmartDataDbDoc<Jwt, plugins.lointRece
public blocked: boolean = false;
@plugins.smartdata.svDb()
public data: plugins.lointReception.data.IJwt['data'];
public data: plugins.idpInterfaces.data.IJwt['data'];
public async block() {
this.blocked = true;
+8 -8
View File
@@ -21,7 +21,7 @@ export class JwtManager {
constructor(receptionRefArg: Reception) {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler<plugins.lointReception.request.IReq_RefreshJwt>(
this.typedrouter.addTypedHandler<plugins.idpInterfaces.request.IReq_RefreshJwt>(
new plugins.typedrequest.TypedHandler(
'refreshJwt',
async (requestArg) => {
@@ -34,7 +34,7 @@ export class JwtManager {
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_GetPublicKeyForValidation>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_GetPublicKeyForValidation>(
'getPublicKeyForValidation',
async (requestArg) => {
// TODO control backend token
@@ -46,7 +46,7 @@ export class JwtManager {
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_PushOrGetJwtIdBlocklist>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_PushOrGetJwtIdBlocklist>(
'pushOrGetJwtIdBlocklist',
async (requestArg) => {
// TODO control backend token
@@ -60,7 +60,7 @@ export class JwtManager {
public async pushPublicKeyToClients() {
const targetConnections =
await this.receptionRef.options.websiteServer.typedserver.typedsocket.findAllTargetConnectionsByTag<plugins.lointReception.tags.ITag_LolePubapi>(
await this.receptionRef.options.websiteServer.typedserver.typedsocket.findAllTargetConnectionsByTag<plugins.idpInterfaces.tags.ITag_LolePubapi>(
'lole-reception',
{
backendToken: '',
@@ -68,7 +68,7 @@ export class JwtManager {
);
for (const targetConnection of targetConnections) {
const pushPublicKeyTr =
this.receptionRef.options.websiteServer.typedserver.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_PushPublicKeyForValidation>(
this.receptionRef.options.websiteServer.typedserver.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_PushPublicKeyForValidation>(
'pushPublicKeyForValidation',
targetConnection
);
@@ -80,7 +80,7 @@ export class JwtManager {
public async pushBlockedJwtIdListToClients() {
const targetConnections =
await this.receptionRef.options.websiteServer.typedserver.typedsocket.findAllTargetConnectionsByTag<plugins.lointReception.tags.ITag_LolePubapi>(
await this.receptionRef.options.websiteServer.typedserver.typedsocket.findAllTargetConnectionsByTag<plugins.idpInterfaces.tags.ITag_LolePubapi>(
'lole-reception',
{
backendToken: '',
@@ -88,7 +88,7 @@ export class JwtManager {
);
for (const targetConnection of targetConnections) {
const pushPublicKeyTr =
this.receptionRef.options.websiteServer.typedserver.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_PushOrGetJwtIdBlocklist>(
this.receptionRef.options.websiteServer.typedserver.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_PushOrGetJwtIdBlocklist>(
'pushOrGetJwtIdBlocklist',
targetConnection
);
@@ -121,7 +121,7 @@ export class JwtManager {
}
public async verifyJWTAndGetData(jwtArg: string): Promise<Jwt> {
const jwtData: plugins.lointReception.data.IJwt = await this.smartjwtInstance.verifyJWTAndGetData(jwtArg);
const jwtData: plugins.idpInterfaces.data.IJwt = await this.smartjwtInstance.verifyJWTAndGetData(jwtArg);
const jwt = await Jwt.getInstance({
id: jwtData.id,
});
+2 -2
View File
@@ -8,7 +8,7 @@ import { User } from './classes.user.js';
@plugins.smartdata.Manager()
export class LoginSession extends plugins.smartdata.SmartDataDbDoc<
LoginSession,
plugins.lointReception.data.ILoginSession,
plugins.idpInterfaces.data.ILoginSession,
LoginSessionManager
> {
// ======
@@ -55,7 +55,7 @@ export class LoginSession extends plugins.smartdata.SmartDataDbDoc<
public id: string;
@plugins.smartdata.svDb()
public data: plugins.lointReception.data.ILoginSession['data'] = {
public data: plugins.idpInterfaces.data.ILoginSession['data'] = {
userId: null,
validUntil: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ weeks: 1 }),
invalidated: false,
+14 -9
View File
@@ -1,6 +1,7 @@
import * as plugins from '../plugins.js';
import { LoginSession } from './classes.loginsession.js';
import { Reception } from './classes.reception.js';
import { logger } from './logging.js';
export class LoginSessionManager {
// refs
@@ -25,7 +26,7 @@ export class LoginSessionManager {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmailOrUsernameAndPassword>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
'loginWithEmailOrUsernameAndPassword',
async (requestData) => {
let user = await this.receptionRef.userManager.CUser.getInstance({
@@ -78,15 +79,17 @@ export class LoginSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmail>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmail>(
'loginWithEmail',
async (requestDataArg) => {
logger.log('info', `loginWithEmail requested for: ${requestDataArg.email}`);
const existingUser = await this.receptionRef.userManager.CUser.getInstance({
data: {
email: requestDataArg.email,
},
});
if (existingUser) {
logger.log('info', `loginWithEmail found user: ${existingUser.data.email}`);
this.emailTokenMap.findOneAndRemoveSync(
(itemArg) => itemArg.email === existingUser.data.email
);
@@ -103,6 +106,8 @@ export class LoginSessionManager {
);
});
this.receptionRef.receptionMailer.sendLoginWithEMailMail(existingUser, loginEmailToken);
} else {
logger.log('info', `loginWithEmail did not find user: ${requestDataArg.email}`);
}
return {
status: 'ok',
@@ -116,7 +121,7 @@ export class LoginSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmailAfterEmailTokenAquired>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_LoginWithEmailAfterEmailTokenAquired>(
'loginWithEmailAfterEmailTokenAquired',
async (requestArg) => {
const tokenObject = this.emailTokenMap.findSync((itemArg) => {
@@ -140,7 +145,7 @@ export class LoginSessionManager {
)
);
this.typedRouter.addTypedHandler<plugins.lointReception.request.ILogoutRequest>(
this.typedRouter.addTypedHandler<plugins.idpInterfaces.request.ILogoutRequest>(
new plugins.typedrequest.TypedHandler('logout', async (requestDataArg) => {
const loginSession = await this.CLoginSession.getLoginSessionByRefreshToken(requestDataArg.refreshToken);
await loginSession.invalidate();
@@ -148,7 +153,7 @@ export class LoginSessionManager {
})
);
this.typedRouter.addTypedHandler<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.typedRouter.addTypedHandler<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>(
new plugins.typedrequest.TypedHandler(
'exchangeRefreshTokenAndTransferToken',
async (requestDataArg) => {
@@ -184,7 +189,7 @@ export class LoginSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_ResetPassword>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_ResetPassword>(
'resetPassword',
async (requestDataArg) => {
const emailOfPasswordToReset = requestDataArg.email;
@@ -222,7 +227,7 @@ export class LoginSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_SetNewPassword>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_SetNewPassword>(
'setNewPassword',
async (requestData) => {
return {
@@ -236,7 +241,7 @@ export class LoginSessionManager {
* returns a device id by simply returning a uuid4
*/
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_ObtainDeviceId>('obtainDeviceId', async (reqData) => {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_ObtainDeviceId>('obtainDeviceId', async (reqData) => {
reqData;
return {
deviceId: {
@@ -247,7 +252,7 @@ export class LoginSessionManager {
)
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_AttachDeviceId>('attachDeviceId', async (reqData) => {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_AttachDeviceId>('attachDeviceId', async (reqData) => {
// TODO: Blocked by proper JWT handling
reqData.jwt;
return {
+3 -3
View File
@@ -5,7 +5,7 @@ import { User } from './classes.user.js';
@plugins.smartdata.Manager()
export class Organization extends plugins.smartdata.SmartDataDbDoc<
Organization,
plugins.lointReception.data.IOrganization,
plugins.idpInterfaces.data.IOrganization,
OrganizationManager
> {
public static async createNewOrganizationForUser(
@@ -28,10 +28,10 @@ export class Organization extends plugins.smartdata.SmartDataDbDoc<
// INSTANCE
@plugins.smartdata.unI()
id: plugins.lointReception.data.IOrganization['id'];
id: plugins.idpInterfaces.data.IOrganization['id'];
@plugins.smartdata.svDb()
data: plugins.lointReception.data.IOrganization['data'];
data: plugins.idpInterfaces.data.IOrganization['data'];
public async checkIfUserIsAdmin(userArg: User) {
const role = await this.manager.receptionRef.roleManager.getRoleForUserAndOrg(userArg, this);
+2 -2
View File
@@ -17,7 +17,7 @@ export class OrganizationManager {
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_CreateOrganization>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_CreateOrganization>(
'createOrganization',
async (requestArg) => {
const nameIsAvailable = async () => {
@@ -64,7 +64,7 @@ export class OrganizationManager {
)
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_GetOrganizationById>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_GetOrganizationById>(
'getOrganizationById',
async (requestArg) => {
const verifiedJwt = await this.receptionRef.jwtManager.verifyJWTAndGetData(
+2
View File
@@ -21,6 +21,7 @@ export interface IReceptionOptions {
name: string;
mongoDescriptor: plugins.smartdata.IMongoDescriptor;
websiteServer: plugins.typedserver.utilityservers.UtilityWebsiteServer;
baseUrl: string;
}
export class Reception {
@@ -55,6 +56,7 @@ export class Reception {
* starts the reception instance
*/
public async start() {
await this.szPlatformClient.init(await this.serviceQenv.getEnvVarOnDemand('SERVEZONE_PLATFROM_AUTHORIZATION'));
logger.log('info', 'starting reception');
logger.log('info', 'adding typedrouter to website server');
this.options.websiteServer.typedrouter.addTypedRouter(this.typedrouter);
+4 -3
View File
@@ -152,9 +152,9 @@ export class ReceptionMailer {
</html>
`;
public sendRegistrationEmail(signupSessionArg: RegistrationSession, validationTokenArg: string) {
public async sendRegistrationEmail(signupSessionArg: RegistrationSession, validationTokenArg: string) {
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
from: 'workspace.global <noreply@mail.workspace.global>',
from: `idp.global@${this.receptionRef.options.baseUrl} <noreply@mail.workspace.global>`,
title: 'Verify your Email Address!',
to: signupSessionArg.emailAddress,
body: this.createBodyString(`
@@ -163,7 +163,7 @@ export class ReceptionMailer {
}">${signupSessionArg.emailAddress}</a></h1>
<p>It looks like you requested to register an account with us. We just want to make sure it really was you.</p>
<p>In case it was you, <b>please continue with the registration process by clicking the button below</b>. Otherwise, please ignore this email.</p>
<a href="https://registration.workspace.global/finishregistration?validationtoken=${encodeURI(
<a href="${this.receptionRef.options.baseUrl}/finishregistration?validationtoken=${encodeURI(
validationTokenArg
)}"><div class="button">
continue with registration
@@ -229,6 +229,7 @@ export class ReceptionMailer {
}
public sendLoginWithEMailMail(userArg: User, validationTokenArg: string) {
console.log(`sending login email to ${userArg.data.email}`);
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
from: 'workspace.global <noreply@mail.workspace.global>',
title: 'Click to login!',
+1 -1
View File
@@ -68,7 +68,7 @@ export class RegistrationSession {
'announced';
public collectedData: {
userData: plugins.lointReception.data.IUser['data'];
userData: plugins.idpInterfaces.data.IUser['data'];
} = {
userData: {
username: null,
@@ -14,7 +14,7 @@ export class RegistrationSessionManager {
this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_FirstRegistration>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_FirstRegistration>(
'firstRegistrationRequest',
async (requestData) => {
// check for exiting User
@@ -60,9 +60,10 @@ export class RegistrationSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_AfterRegistrationEmailClicked>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_AfterRegistrationEmailClicked>(
'afterRegistrationEmailClicked',
async (requestData) => {
console.log(requestData);
const signupSession = await this.registrationSessions.find(async (itemArg) =>
itemArg.validateEmailToken(requestData.token)
);
@@ -82,7 +83,7 @@ export class RegistrationSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_SetDataForRegistration>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_SetDataForRegistration>(
'setDataForRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
@@ -110,7 +111,7 @@ export class RegistrationSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_MobileVerificationForRegistration>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_MobileVerificationForRegistration>(
'mobileVerificationForRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
@@ -156,7 +157,7 @@ export class RegistrationSessionManager {
);
this.typedRouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_FinishRegistration>(
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_FinishRegistration>(
'finishRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
+2 -2
View File
@@ -3,12 +3,12 @@ import * as plugins from '../plugins.js';
@plugins.smartdata.Manager()
export class Role extends plugins.smartdata.SmartDataDbDoc<
Role,
plugins.lointReception.data.IRole
plugins.idpInterfaces.data.IRole
> {
@plugins.smartdata.unI()
id: string;
@plugins.smartdata.svDb()
data: plugins.lointReception.data.IRole['data'];
data: plugins.idpInterfaces.data.IRole['data'];
}
+1 -1
View File
@@ -19,7 +19,7 @@ export class RoleManager {
action: 'create' | 'change' | 'delete';
userId: string;
organizationId: string;
role: plugins.lointReception.data.IRole['data']['role'];
role: plugins.idpInterfaces.data.IRole['data']['role'];
}) {
let returnRole: Role;
switch (optionsArg.action) {
+3 -3
View File
@@ -8,11 +8,11 @@ import { UserManager } from './classes.usermanager.js';
@plugins.smartdata.Manager()
export class User extends plugins.smartdata.SmartDataDbDoc<
User,
plugins.lointReception.data.IUser
plugins.idpInterfaces.data.IUser
> {
// STATIC
public static async createNewUserForUserData(
userDataArg: plugins.lointReception.data.IUser['data']
userDataArg: plugins.idpInterfaces.data.IUser['data']
): Promise<User> {
const newUser = new User();
newUser.id = plugins.smartunique.shortId();
@@ -40,7 +40,7 @@ export class User extends plugins.smartdata.SmartDataDbDoc<
id: string;
@plugins.smartdata.svDb()
public data: plugins.lointReception.data.IUser['data'];
public data: plugins.idpInterfaces.data.IUser['data'];
constructor() {
super();
+26 -2
View File
@@ -19,8 +19,9 @@ export class UserManager {
constructor(receptionRefArg: Reception) {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler<plugins.lointReception.request.IReq_GetRolesAndOrganizationsForUserId>(
this.typedrouter.addTypedHandler<plugins.idpInterfaces.request.IReq_GetRolesAndOrganizationsForUserId>(
new plugins.typedrequest.TypedHandler('getRolesAndOrganizationsForUserId', async reqArg => {
console.log('user manager: getting roles and orgs');
const user = await this.getUserByJwtValidation(reqArg.jwt);
const organizations = await this.receptionRef.organizationmanager.getAllOrganizationsForUser(
user
@@ -32,6 +33,29 @@ export class UserManager {
}
})
)
this.typedrouter.addTypedHandler<plugins.idpInterfaces.request.IReq_WhoIs>(
new plugins.typedrequest.TypedHandler('whoIs', async reqArg => {
const user = await this.getUserByJwtValidation(reqArg.jwt);
if (!user) {
throw new plugins.typedrequest.TypedResponseError('User not found');
}
return {
user: {
id: user.id,
data: {
name: user.data.name,
username: user.data.username,
email: user.data.email,
mobileNumber: user.data.mobileNumber,
connectedOrgs: user.data.connectedOrgs,
status: null,
password: null,
} as plugins.idpInterfaces.data.IUser['data']
}
}
})
)
}
/**
@@ -50,7 +74,7 @@ export class UserManager {
* faster than the "getUserByJwt"
*/
public async getUserByJwtValidation(jwtStringArg: string) {
const jwtDataArg: plugins.lointReception.data.IJwt = await this.receptionRef.jwtManager.smartjwtInstance.verifyJWTAndGetData(jwtStringArg);
const jwtDataArg: plugins.idpInterfaces.data.IJwt = await this.receptionRef.jwtManager.smartjwtInstance.verifyJWTAndGetData(jwtStringArg);
const resultingUser = await this.CUser.getInstance({
id: jwtDataArg.data.userId
});
+59 -43
View File
@@ -4,27 +4,27 @@ import * as plugins from './plugins.js';
export class IdpClient {
// INSTANCE PRIVATE
private helpers = {
async extractDataFromJwtString(jwtString: string): Promise<plugins.lointReception.data.IJwt> {
async extractDataFromJwtString(jwtString: string): Promise<plugins.idpInterfaces.data.IJwt> {
return plugins.webjwt.getDataFromJwtString(jwtString);
},
};
// INSTANCE PUBLIC
public appData: plugins.lointReception.data.IApp;
public appData: plugins.idpInterfaces.data.IApp;
public rolesReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
public organizationsReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
public receptionTrUrl: string;
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.lointReception.data.IApp) {
this.receptionTrUrl = receptionBaseUrlArg
if (this.receptionTrUrl.endsWith('/')) {
this.receptionTrUrl = this.receptionTrUrl.slice(0, -1);
public parsedReceptionUrl: plugins.smarturl.Smarturl;
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.idpInterfaces.data.IApp) {
if (receptionBaseUrlArg.endsWith('/')) {
receptionBaseUrlArg = receptionBaseUrlArg.slice(0, -1);
}
if (!this.receptionTrUrl.endsWith('/typedrequest')) {
this.receptionTrUrl = `${this.receptionTrUrl}/typedrequest`;
if (!receptionBaseUrlArg.endsWith('/typedrequest')) {
receptionBaseUrlArg = `${receptionBaseUrlArg}/typedrequest`;
}
console.log(`reception client connecting to ${this.receptionTrUrl}`);
this.parsedReceptionUrl = plugins.smarturl.Smarturl.createFromUrl(receptionBaseUrlArg);
console.log(`reception client connecting to ${this.parsedReceptionUrl.toString()}`);
if (!appDataArg) {
appDataArg = {
id: '', // TODO
@@ -39,6 +39,11 @@ export class IdpClient {
public requests = new IdpRequests(this);
public checkWetherOnReceptionDomain() {
return plugins.smarturl.Smarturl.createFromUrl(window.location.href).hostname ===
this.parsedReceptionUrl.hostname;
}
/**
* app data can be transferred when redirecting to the sso domain using query params
* this message retrieves the app data when on the sso domain
@@ -73,26 +78,26 @@ export class IdpClient {
public typedrouter = new plugins.typedrequest.TypedRouter();
public statusObservable =
new plugins.smartrx.rxjs.Subject<plugins.lointReception.data.TLoginStatus>();
new plugins.smartrx.rxjs.Subject<plugins.idpInterfaces.data.TLoginStatus>();
public ssoStore = new plugins.webstore.WebStore({
storeName: 'wgsso',
dbName: 'wgsso',
storeName: 'idpglobalStore',
dbName: 'main',
});
public async storeJwt(jwtString: string) {
await this.ssoStore.set('wgJwt', jwtString);
await this.ssoStore.set('idpJwt', jwtString);
}
public async getJwt(): Promise<string> {
return await this.ssoStore.get('wgJwt');
return await this.ssoStore.get('idpJwt');
}
public async getJwtData(): Promise<plugins.lointReception.data.IJwt> {
public async getJwtData(): Promise<plugins.idpInterfaces.data.IJwt> {
return this.helpers.extractDataFromJwtString(await this.getJwt());
}
public async deleteJwt() {
await this.ssoStore.delete('wgJwt');
await this.ssoStore.delete('idpJwt');
console.log('removed jwt');
}
@@ -116,14 +121,14 @@ export class IdpClient {
}
public async refreshJwt(refreshTokenArg?: string): Promise<string> {
let extractedJwt: plugins.lointReception.data.IJwt;
let extractedJwt: plugins.idpInterfaces.data.IJwt;
if (!refreshTokenArg) {
extractedJwt = await this.helpers.extractDataFromJwtString(await this.getJwt());
}
const refreshJwtReq =
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
`${this.receptionTrUrl}/typedrequest`,
new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
this.parsedReceptionUrl.toString(),
'refreshJwt'
);
const response = await refreshJwtReq.fire({
@@ -141,12 +146,12 @@ export class IdpClient {
/**
* can be used to switch between pages
*/
public async getTransferToken(appDataArg?: plugins.lointReception.data.IApp): Promise<string> {
public async getTransferToken(appDataArg?: plugins.idpInterfaces.data.IApp): Promise<string> {
const jwt = await this.performJwtHousekeeping();
const extractedJwt = await this.helpers.extractDataFromJwtString(jwt);
const getTransferToken =
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
`${this.receptionTrUrl}/typedrequest`,
new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.parsedReceptionUrl.toString(),
'exchangeRefreshTokenAndTransferToken'
);
const response = await getTransferToken.fire({
@@ -183,8 +188,8 @@ export class IdpClient {
const transferToken = url.searchParams['transfertoken'];
if (transferToken) {
const getTransferToken =
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
`${this.receptionTrUrl}/typedrequest`,
new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.parsedReceptionUrl.toString(),
'exchangeRefreshTokenAndTransferToken'
);
const response = await getTransferToken.fire({
@@ -214,7 +219,8 @@ export class IdpClient {
}
/**
* forces the current user to login
* determines if the user is logged in
* accepts boolean to optionally require login
* @param requireLoginArg
* @returns
*/
@@ -231,15 +237,14 @@ export class IdpClient {
} else {
if (requireLoginArg) {
const urlInstance = plugins.smarturl.Smarturl.createFromUrl(
'https://sso.workspace.global/',
this.parsedReceptionUrl.clone().set('path', '/login').toString(),
{
searchParams: {
appdata: plugins.smartjson.stringifyBase64(this.appData),
action: 'login',
},
}
);
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
if (!globalThis.location.href.startsWith(this.parsedReceptionUrl.toString())) {
globalThis.location.href = urlInstance.toString();
}
}
@@ -252,22 +257,17 @@ export class IdpClient {
* logs out the current user
*/
public async logout() {
const urlInstance = plugins.smarturl.Smarturl.createFromUrl('https://sso.workspace.global/', {
searchParams: {
appdata: plugins.smartjson.stringifyBase64(this.appData),
action: 'logout',
},
});
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
const idpLogoutUrl = this.parsedReceptionUrl.clone().set('path', '/logout');
if (!globalThis.location.href.startsWith(idpLogoutUrl.origin)) {
// we are somewhere in an app
await this.deleteJwt();
globalThis.location.href = urlInstance.toString();
globalThis.location.href = idpLogoutUrl.toString();
} else {
// we are in the sso page
await this.enableTypedSocket();
console.log(`logging out against ${this.receptionTrUrl}`)
console.log(`logging out against ${this.parsedReceptionUrl.toString()}`);
const logoutTr =
this.typedsocket.createTypedRequest<plugins.lointReception.request.ILogoutRequest>(
this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.ILogoutRequest>(
'logout'
);
await logoutTr.fire({
@@ -281,6 +281,9 @@ export class IdpClient {
} else {
console.error('no appData provided. Not redirecting after logout.');
}
if (window.location.href.startsWith(idpLogoutUrl.origin)) {
window.location.href = this.parsedReceptionUrl.origin;
}
}
}
@@ -292,7 +295,7 @@ export class IdpClient {
this.typedsocketDeferred.claim();
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(
this.typedrouter,
`${this.receptionTrUrl}/`
this.parsedReceptionUrl.toString()
);
this.typedsocketDeferred.resolve(this.typedsocket);
return this.typedsocketDeferred.promise;
@@ -312,7 +315,7 @@ export class IdpClient {
) {
await this.typedsocketDeferred.promise;
const validateOrg =
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_CreateOrganization>(
this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_CreateOrganization>(
'createOrganization'
);
const response = await validateOrg.fire({
@@ -329,9 +332,10 @@ export class IdpClient {
* gets the current OrganizationRoles
*/
public async getRolesAndOrganizations() {
console.log('idpclient: getting roles and orgs...');
await this.typedsocketDeferred.promise;
const rolesAndOrganizationsForUserId =
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_GetRolesAndOrganizationsForUserId>(
this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_GetRolesAndOrganizationsForUserId>(
'getRolesAndOrganizationsForUserId'
);
const response = await rolesAndOrganizationsForUserId.fire({
@@ -347,7 +351,7 @@ export class IdpClient {
public async updatePaddleCheckoutId(orgIdArg: string, checkoutIdArg: string) {
await this.typedsocketDeferred.promise;
const updateBillingPlan =
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_UpdatePaymentMethod>(
this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_UpdatePaymentMethod>(
'updatePaymentMethod'
);
const response = await updateBillingPlan.fire({
@@ -359,4 +363,16 @@ export class IdpClient {
});
return response;
}
public async whoIs() {
await this.typedsocketDeferred.promise;
const whoIs =
this.typedsocket.createTypedRequest<plugins.idpInterfaces.request.IReq_WhoIs>(
'whoIs'
);
const response = await whoIs.fire({
jwt: await this.getJwt(),
});
return response;
}
}
+14 -14
View File
@@ -11,51 +11,51 @@ export class IdpRequests {
}
public get afterRegistrationEmailClicked () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_AfterRegistrationEmailClicked>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_AfterRegistrationEmailClicked>(
this.idpClientArg.parsedReceptionUrl.toString(),
'afterRegistrationEmailClicked'
);
}
public get setData() {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_SetDataForRegistration>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_SetDataForRegistration>(
this.idpClientArg.parsedReceptionUrl.toString(),
'setDataForRegistration'
);
}
public get mobileNumberVerification () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_MobileVerificationForRegistration>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_MobileVerificationForRegistration>(
this.idpClientArg.parsedReceptionUrl.toString(),
'mobileVerificationForRegistration'
);
}
public get finishRegistration() {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_FinishRegistration>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_FinishRegistration>(
this.idpClientArg.parsedReceptionUrl.toString(),
'finishRegistration'
);
}
public get loginWithUserNameAndPassword () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_LoginWithEmailOrUsernameAndPassword>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
this.idpClientArg.parsedReceptionUrl.toString(),
'loginWithEmailOrUsernameAndPassword'
);
}
public get obtainJwt () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
this.idpClientArg.parsedReceptionUrl.toString(),
'refreshJwt'
);
}
public get obtainOneTimeToken () {
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.idpClientArg.receptionTrUrl,
return new plugins.typedrequest.TypedRequest<plugins.idpInterfaces.request.IReq_ExchangeRefreshTokenAndTransferToken>(
this.idpClientArg.parsedReceptionUrl.toString(),
'exchangeRefreshTokenAndTransferToken'
);
}
+2 -2
View File
@@ -1,7 +1,7 @@
// losslessone_private scope
import * as lointReception from '../dist_ts_interfaces/index.js';
import * as idpInterfaces from '../dist_ts_interfaces/index.js';
export { lointReception };
export { idpInterfaces };
// apiglobal scope
import * as typedrequest from '@api.global/typedrequest';
@@ -74,3 +74,13 @@ export interface IReq_GetRolesAndOrganizationsForUserId
organizations: data.IOrganization[];
};
}
export interface IReq_WhoIs {
method: 'whoIs';
request: {
jwt: string;
};
response: {
user: data.IUser;
};
}
+1 -1
View File
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@idp.global/idp.global',
version: '1.1.1',
version: '1.4.1',
description: 'An identity provider software managing user authentications, registrations, and sessions.'
}
+141
View File
@@ -0,0 +1,141 @@
import * as plugins from '../../plugins.js';
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
type TemplateResult
} from '@design.estate/dees-element';
import { LeleAccountNavigation } from './navigation.js';
import * as views from './views/index.js';
import * as accountstate from '../../states/accountstate.js';
import { commitinfo } from '../../../dist_ts/00_commitinfo_data.js';
declare global {
interface HTMLElementTagNameMap {
'idp-accountcontent': IdpAccountContent;
}
}
@customElement('idp-accountcontent')
export class IdpAccountContent extends DeesElement {
public subrouter: plugins.deesDomtools.plugins.smartrouter.SmartRouter;
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
color: #fff;
padding-top: 10px;
padding-bottom: 10px;
height: 100%;
width: 100%;
background: ${cssManager.bdTheme('#eeeeeb', '#000000')}
}
:host([hidden]) {
display: none;
}
.main {
position: absolute;
height: 100%;
width: 100%;
bottom: 0px;
}
lele-accountnavigation {
position: absolute;
bottom: 0px;
left: 0px;
height: 100vh;
width: 200px;
}
.viewcontainer {
will-change: transform;
position: absolute;
right: 0px;
bottom: 0px;
width: calc(100vw - 200px);
height: 100vh;
overflow-y: scroll;
overscroll-behavior: contain;
transition: all 0.3s;
opacity: 1;
}
.viewcontainer.changing {
opacity: 0;
transform: translateY(20px);
}
`,
];
public render(): TemplateResult {
return html`
<style></style>
<div class="main">
<lele-accountnavigation></lele-accountnavigation>
<div class="viewcontainer">
<!--<lele-accountview-subscription></lele-accountview-subscription>-->
</div>
</div>
`;
}
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
super.firstUpdated(_changedProperties);
await this.domtoolsPromise;
this.subrouter = this.domtools.router.createSubRouter('/account');
const viewcontainer: HTMLDivElement = this.shadowRoot.querySelector('.viewcontainer');
const cleanupViews = async () => {
for (const child of Array.from(viewcontainer.children)) {
viewcontainer.removeChild(child);
}
};
viewcontainer.append(new views.BaseView());
console.log(`loaded base view`);
this.subrouter.on('', async () => {
viewcontainer.classList.add('changing');
await this.domtools.convenience.smartdelay.delayFor(300);
console.log('We are viewing the account overview');
await cleanupViews();
viewcontainer.append(new views.BaseView());
viewcontainer.classList.remove('changing');
await this.domtools.convenience.smartdelay.delayFor(300);
});
this.subrouter.on('/org/:orgName/billing', async () => {
viewcontainer.classList.add('changing');
await this.domtools.convenience.smartdelay.delayFor(300);
console.log('We are viewing the billing page');
await cleanupViews();
viewcontainer.append(new views.SubscriptionView());
viewcontainer.classList.remove('changing');
await this.domtools.convenience.smartdelay.delayFor(300);
});
this.subrouter._handleRouteState();
this.registerGarbageFunction(async () => {
this.subrouter.destroy();
})
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './content.js';
export * from './navigation.js';
+221
View File
@@ -0,0 +1,221 @@
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
type TemplateResult,
subscribe,
} from '@design.estate/dees-element';
import * as plugins from '../../plugins.js';
import * as states from '../../states/accountstate.js';
import { IdpState } from '../../states/idp.state.js';
import { commitinfo } from '../../../dist_ts/00_commitinfo_data.js';
declare global {
interface HTMLElementTagNameMap {
'lele-accountnavigation': LeleAccountNavigation;
}
}
@customElement('lele-accountnavigation')
export class LeleAccountNavigation extends DeesElement {
@property()
public options: { text: string; id: string }[] = [
{
id: '1',
text: 'Apps',
},
{
id: '2',
text: 'Users',
},
{
id: '3',
text: 'Activity',
},
{
id: '4',
text: 'Billing & Subscription',
},
];
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
color: ${cssManager.bdTheme('#333', '#fff')};
padding: 10px;
padding-left: 0px;
background: ${cssManager.bdTheme('#eeeeeb', '#000')};
border-right: ${cssManager.bdTheme('1px solid #ccc', '1px solid #111')};
}
:host([hidden]) {
display: none;
}
.logo {
font-family: 'Cal Sans';
letter-spacing: 0.0125em;
font-size: 16px;
text-align: center;
padding: 16px 0px 16px 0px;
margin: -8px -8px -16px 0px;
border-bottom: 1px solid #111111;
cursor: default;
position: relative;
z-index: 10;
}
.logo:hover {
background: ${unsafeCSS(plugins.deesCatalog.colors.dark.blue)};
}
.commitinfo {
text-align: center;
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
font-size: 12px;
padding: 8px;
border-top: ${cssManager.bdTheme('1px solid #ccc', '1px solid #333')};
color: ${cssManager.bdTheme('#666', '#ccc')};
}
.navigationGroupLabel {
width: min-content;
white-space: nowrap;
text-transform: uppercase;
font-size: 12px;
font-weight: 300;
border-bottom: 1px solid;
border-image: linear-gradient(to right, orange, #44444400) 1;
color: ${cssManager.bdTheme('#666', '#ccc')};
margin-bottom: 5px;
padding-top: 32px;
padding-left: 10px;
padding-bottom: 5px;
}
.navigationOption {
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
font-weight: 500;
padding: 8px;
padding-left: 10px;
margin-bottom: 5px;
}
.navigationOption:hover {
cursor: default;
background: ${cssManager.bdTheme('#bbb', plugins.deesCatalog.colors.dark.blue)};
}
dees-input-dropdown {
margin-top: 16px;
margin-bottom: 16px;
margin-left: 8px;
}
`,
];
public async getAccountRouter() {
const host = (this.getRootNode() as any).host;
return (host as any).subrouter;
}
public render(): TemplateResult {
return html`
<style></style>
<div class="commitinfo">idp.global v${commitinfo.version}</div>
<div class="logo">idp.global</div>
<div class="navigationGroupLabel">Account Settings</div>
<div
class="navigationOption"
@click=${async () => {
const subrouter = await this.getAccountRouter();
subrouter.pushUrl('');
}}
>
overview
</div>
<div
class="navigationOption"
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/logout');
}}
>
logout
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
manage roles
</div>
<div
class="navigationOption"
@click=${async () => {
}}
>
create an org
</div>
<div class="navigationGroupLabel">Organization Settings</div>
<dees-input-dropdown
.label=${'choose org:'}
@selectedOption=${(eventArg: CustomEvent) => {
const currentState = states.accountState.getState();
states.accountState.dispatchAction(
states.setSelectedOrg,
currentState.organizations.find((org) => org.data.slug === eventArg.detail.payload)
);
}}
></dees-input-dropdown>
${this.options.map((option) => {
return html` <div class="navigationOption">${option.text}</div> `;
})}
`;
}
public firstUpdated() {
const deesInputDropdown = this.shadowRoot.querySelector('dees-input-dropdown');
const orgToMenuEntry = (orgArg?: plugins.idpInterfaces.data.IOrganization) => {
if (!orgArg) {
return null;
}
return {
option: orgArg.data.name,
key: orgArg.data.slug,
payload: orgArg.data.slug,
};
};
states.accountState
.select((stateArg) => stateArg.organizations)
.pipe(
plugins.deesDomtools.plugins.smartrx.rxjs.ops.map((orgArrayArg) => {
return orgArrayArg.map(orgToMenuEntry);
})
)
.subscribe((menuEntries) => {
deesInputDropdown.options = menuEntries;
});
states.accountState
.select((stateArg) => stateArg.selectedOrg)
.pipe(plugins.deesDomtools.plugins.smartrx.rxjs.ops.map(orgToMenuEntry))
.subscribe((selectedOrgArg) => {
deesInputDropdown.selectedOption = selectedOrgArg;
});
}
}
+29
View File
@@ -0,0 +1,29 @@
import { css } from '@design.estate/dees-element';
export default css`
h1 {
margin-top: 50px;
border-bottom: 1px solid;
border-image: radial-gradient(rgba(136, 136, 136, 0.44), rgba(136, 136, 136, 0)) 1 / 1 / 0 stretch;
padding-bottom: 10px;
font-weight: 500;
}
h2 {
border-top: 1px dotted #666;
padding-top: 16px;
}
p {
line-height: 1.5em;
}
dees-button {
margin-top: 16px;
width: 200px;
}
dees-input-text {
max-width: 400px;
}
`;
+193
View File
@@ -0,0 +1,193 @@
import * as plugins from '../../../plugins.js';
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
render,
subscribe,
} from '@design.estate/dees-element';
import sharedStyles from '../sharedstyles.js';
declare global {
interface HTMLElementTagNameMap {
'lele-accountview-baseview': BaseView;
}
}
import * as state from '../../../states/accountstate.js';
@customElement('lele-accountview-baseview')
export class BaseView extends DeesElement {
@property({
type: Array,
})
subscriptions: any[] = [
{
organization: 'org1',
'subscription type': 'workspace.global SaaS',
price: '4€',
userFactor: 4,
total: '16.00€',
},
{
organization: 'org1',
'subscription type': 'workspace.global IaaS Base Access',
price: '0€',
userFactor: 4,
total: '0€',
},
{
organization: 'org1',
'subscription type': 'workspace.global SLA Senior',
price: '2000€',
userFactor: 'none',
total: '2000.00€',
},
];
public static styles = [
cssManager.defaultStyles,
sharedStyles,
css`
:host {
display: block;
max-width: 900px;
margin: auto;
color: ${cssManager.bdTheme('#333', '#fff')};
}
.slug {
color: orange;
}
.orgGrid {
display: grid;
grid-gap: 16px;
grid-template-columns: ${cssManager.cssGridColumns(2, 16)};
}
.org {
padding: 16px;
border-top: 1px solid #444;
background: ${cssManager.bdTheme('#ccc', '#222')};
border-radius: 16px;
}
.org:hover {
cursor: default;
background: ${cssManager.bdTheme('#CCC', '#333')};
}
`,
];
public render() {
return html`
<div class="viewHost">
</div> `;
}
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
await this.domtoolsPromise;
super.firstUpdated(_changedProperties);
const viewHost: HTMLDivElement = this.shadowRoot.querySelector('.viewHost');
await state.accountState.dispatchAction(state.getOrganizationsAction, null);
console.log('got orgs');
if (state.accountState.getState().organizations.length === 0) {
render(
html`
<h1>Setup Your Account</h1>
<p>
There are no organizations for your account. Please create one now. Alternatively you
can ask an admin of an existing organization to invite you.
</p>
<dees-form>
<dees-input-text .label=${'Organization Name'} .key=${'orgName'}></dees-input-text>
</dees-form>
<p>
The organization slug corresponds to the organization name:<br />
<span class="slug"
>${subscribe(
state.accountState.select((stateArg) => stateArg.newOrg.chosenSlug)
)}</span
>
</p>
<span class="hint"></span>
<dees-button .disabled=${true}>Create the Organization</dees-button>
`,
viewHost
);
const subscriptions: plugins.deesDomtools.plugins.smartrx.rxjs.Subscription[] = [];
const form = this.shadowRoot.querySelector('dees-form');
const orgInput = this.shadowRoot.querySelector('dees-input-text');
const hint = this.shadowRoot.querySelector('.hint');
const button = this.shadowRoot.querySelector('dees-button');
const newOrgSubscription = state.accountState
.select((stateArg) => stateArg.newOrg)
.subscribe((data) => {
if (data.chosenSlug) {
hint.innerHTML = 'Waiting: Validating...';
} else {
hint.innerHTML = 'Hint: Enter a valid organization name.';
}
if (data.validated && data.validationOk) {
hint.innerHTML =
'Success: Name is available. Please click the button to create the organization.';
button.disabled = false;
} else if (!data.validated || !data.validationOk) {
hint.innerHTML = `Info: Name not available. Please choose another one.`;
button.disabled = true;
}
});
subscriptions.push(newOrgSubscription);
const formSubscription = form.changeSubject.subscribe(async (dataArg: any) => {
await state.accountState.dispatchAction(state.setNewOrgName, dataArg.orgName);
});
subscriptions.push(formSubscription);
button.addEventListener('clicked', async () => {
orgInput.disabled = true;
button.text = 'creating org...';
button.status = 'pending';
hint.innerHTML = 'Waiting for creation of the organization...';
await state.accountState.dispatchAction(state.manifestNewOrgName, null);
hint.innerHTML = `The Organization with name ${
state.accountState.getState().organizations[0].data.name
} has been created!`;
button.text = 'created!';
button.status = 'success';
const parentElement = (this.getRootNode() as any).host;
parentElement.subrouter.pushUrl(
`/org/${state.accountState.getState().organizations[0].data.slug}/billing`
);
});
} else {
render(
html`
<h1>Select An Organization</h1>
<div class="orgGrid">
${state.accountState.getState().organizations.map((orgArg) => {
return html`
<div
class="org"
@click=${() => {
state.accountState.dispatchAction(state.setSelectedOrg, orgArg);
const parentElement = (this.getRootNode() as any).host;
parentElement.subrouter.pushUrl(`/org/${orgArg.data.slug}/billing`);
}}
>
<dees-icon .iconFA=${"wallet"} style="display: inline-block; transform: translateY(3px); padding-right: 4px;"></dees-icon> ${orgArg.data.name}
</div>
`;
})}
</div>
`,
viewHost
);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './baseview.js';
export * from './orgsetup.js';
export * from './paddlesetup.js';
export * from './subscriptions.js';
@@ -0,0 +1,94 @@
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
} from '@design.estate/dees-element';
import * as plugins from '../../../plugins.js';
import sharedStyles from '../sharedstyles.js';
import * as state from '../../../states/accountstate.js';
declare global {
interface HTMLElementTagNameMap {
'lele-accountview-paddlesetup': PaddleSetupView;
}
}
@customElement('lele-accountview-paddlesetup')
export class PaddleSetupView extends DeesElement {
public static styles = [
cssManager.defaultStyles,
sharedStyles,
css`
:host {
display: block;
max-width: 900px;
margin: auto;
color: ${cssManager.bdTheme('#333', '#fff')};
}
`,
];
public render() {
return html`
<h1>-> Paddle Setup</h1>
<p>
In order to use workspace.global <b>with paid features</b>, you need to setup a Paddle
subscription. A Paddle connection is bound to an organization.
</p>
<p>
The base price of a Paddle Subscription is always 0€. Any charges that occur will be billed
as an extra charge on top of your free base subscription
<b>on a monthly date of your choosing</b>.
</p>
<p>
Since Paddle acts as merchant of record, your invoices will read Paddle as Creditor, and you
as Debitor.
</p>
<h2>Why are we using Paddle?</h2>
<p>
Paddle takes care of tax compliance for us. This allows us to sell our products world wide
while Paddle makes sure any sales are in compliance with local laws.
</p>
<dees-button>Let's do it!</dees-button>
`;
}
/**
*
*/
public async firstUpdated() {
await this.domtoolsPromise;
const paddleButton = this.shadowRoot.querySelector('dees-button');
const openPaddle = async () => {
await this.domtools.setExternalScript('https://cdn.paddle.com/paddle/paddle.js');
globalThis.Paddle.Setup({
vendor: 30954,
eventCallback: async (dataArg) => {
// The data.event will specify the event type
if (dataArg.event === 'Checkout.Complete') {
const data: plugins.idpInterfaces.data.IPaddleCheckoutData = dataArg.eventData;
const paddleIframe = document.body.querySelector('iframe');
document.body.removeChild(paddleIframe);
paddleButton.status = 'pending';
paddleButton.text = 'Processing...';
await state.accountState.dispatchAction(state.updatePaddleCheckoutId, data.checkout.id);
paddleButton.status = 'success';
paddleButton.text = 'Paddle connected!'
}
},
});
globalThis.Paddle.Checkout.open({
product: 561076,
email: 'phil@kunz.io',
});
};
paddleButton.addEventListener('clicked', async () => {
openPaddle();
});
}
}
@@ -0,0 +1,93 @@
import {
customElement,
DeesElement,
property,
html,
cssManager,
unsafeCSS,
css,
} from '@design.estate/dees-element';
import sharedStyles from '../sharedstyles.js';
import * as state from '../../../states/accountstate.js';
declare global {
interface HTMLElementTagNameMap {
'lele-accountview-subscription': SubscriptionView;
}
}
@customElement('lele-accountview-subscription')
export class SubscriptionView extends DeesElement {
@property({
type: Array,
})
subscriptions: any[] = [{
organization: 'org1',
'subscription type': 'workspace.global SaaS',
price: '4€',
userFactor: 4,
total: '16.00€'
}, {
organization: 'org1',
'subscription type': 'workspace.global IaaS Base Access',
price: '0€',
userFactor: 4,
total: '0€'
}, {
organization: 'org1',
'subscription type': 'workspace.global SLA Senior',
price: '2000€',
userFactor: 'none',
total: '2000.00€'
}];
public static styles = [
cssManager.defaultStyles,
sharedStyles,
css`
:host {
display: block;
max-width: 900px;
margin: auto;
color: ${cssManager.bdTheme('#333', '#fff')};
}
`
]
public render() {
return html`
<h1>-> Billing & Subscription</h1>
This page allows you to setup how you are billed for any workspace.global charges.
<h2>PaymentMethod</h2>
<p>Our customer-side billing is handled by paddle.com. You subscribe to a free plan there,
and we will bill any occurring charges as an extra on the monthly date of your choosing.
Paddle.com will take care of proper VAT invoices that will allow for VAT reduction according to the law.</p>
<h3>Paddle</h3>
<dees-button @click=${async () => {
await this.domtoolsPromise;
this.domtools.router.pushUrl(`/org/${state.accountState.getState().selectedOrg.data.slug}/paddlesetup`)
}}>set up paddle.com</dees-button>
<h3>Enterprise billing</h3>
Once you have 100 or more Pro Plan users, you can request custom Enterprise billing for your organization here. Note: You are currently not eligible.
<h2>Subscriptions</h2>
<p>
The total price of a subscription already includes all taxes. If you are a VAT registered business,
the actual price might be cheaper in case you can claim VAT exemption from the purchase.
</p>
<p>
Note: Subscriptions are tied to prganizations. You are only seeing subcriptions regarding ${'org1'} right now.
To see other organization, select the respective organization at the top left of this page.
</p>
<dees-table .heading1=${'Subscriptions'} .heading2=${`for organization ${'org1'}`} .data=${this.subscriptions}></dees-table>
<dees-button>Add subscription</dees-button>
<h2>Accrued IaaS Usage</h2>
<p>Note: The accrued IaaS Usage will be charged by adjusting the workspsace.gobal IaaS Postpaid Access price prior the renewal date.</p>
<dees-table .heading1=${'Subscriptions'} .heading2=${`for organization ${'org1'}`} .data=${this.subscriptions}></dees-table>
<h2>Upcoming Billable Items</h2>
<h2>Past Invoices</h2>
`;
}
}
+146
View File
@@ -0,0 +1,146 @@
import * as plugins from '../plugins.js';
import {
customElement,
DeesElement,
property,
html,
type TemplateResult,
css,
cssManager,
query,
} from '@design.estate/dees-element';
import { commitinfo } from '../../dist_ts/00_commitinfo_data.js';
import { IdpState } from '../states/idp.state.js';
declare global {
interface HTMLElementTagNameMap {
'idp-centercontainer': IdpCenterContainer;
}
}
@customElement('idp-centercontainer')
export class IdpCenterContainer extends DeesElement {
public static demo = () => html`<idp-centercontainer></idp-centercontainer>`;
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Sans';
position: absolute;
width: 100%;
height: 100%;
}
.mainContainer {
position: absolute;
top: 0px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: all 0.2s;
transition-delay: 0.05s;
transform: translate3d(0px, 8px, 0px);
pointer-events: none;
}
.mainContainer.show {
opacity: 1;
pointer-events: all;
transform: translate3d(0px, 0px, 0px);
}
.loginblock {
max-width: 500px;
flex-grow: 1;
transform: translate3d(0px, 0px, 0px);
transition: all 0.2s;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
background: ${cssManager.bdTheme('#ffffff', '#111111')};
border-top: 1px solid ${cssManager.bdTheme('#ffffff', '#222222')};
border-radius: 16px;
overflow: hidden;
}
h1 {
font-size: 24px;
font-family: 'Cal Sans';
text-align: center;
letter-spacing:0.0125em;
}
.contentSpacer {
padding: 0px 0px 16px 0px;
}
.legalinfo {
text-align: center;
margin: auto;
color: ${cssManager.bdTheme('#666', '#ccc')};
font-size: 12px;
line-height: 100%;
padding: 8px;
background: ${cssManager.bdTheme('#f5f5f5', '#111')};
border-top: 1px solid ${cssManager.bdTheme('#ccc', '#222222')};
color: ${cssManager.bdTheme('#666', '#888')};
}
.legalinfo a {
color: ${cssManager.bdTheme('#666', '#ccc')};
text-decoration: none;
}
`,
];
render() {
return html`
<div class="mainContainer">
<div class="loginblock">
<h1>idp.global</h1>
<div class="contentSpacer">
<slot></slot>
</div>
<div class="legalinfo">
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
| <a href="https://task.vc/" target="_blank">Company Website</a>
| <a href="https://support.task.vc/" target="_blank">Support</a>
| idp.global v${commitinfo.version}
</div>
</div>
</div>
`;
}
public async show() {
await this.updateComplete;
const domtoolsInstance = await this.domtoolsPromise;
const done = plugins.smartpromise.defer();
requestAnimationFrame(async () => {
this.shadowRoot.querySelector('.mainContainer').classList.add('show');
await domtoolsInstance.convenience.smartdelay.delayFor(250);
done.resolve();
});
return done.promise;
}
public async hide() {
await this.updateComplete;
const domtoolsInstance = await this.domtoolsPromise;
const done = plugins.smartpromise.defer();
requestAnimationFrame(async () => {
this.shadowRoot.querySelector('.mainContainer').classList.remove('show');
await domtoolsInstance.convenience.smartdelay.delayFor(250);
done.resolve();
});
return done.promise;
}
}
-264
View File
@@ -1,264 +0,0 @@
import * as plugins from '../plugins.js';
import {
customElement,
DeesElement,
property,
html,
type TemplateResult,
css,
cssManager,
query,
} from '@design.estate/dees-element';
import { commitinfo } from '../../dist_ts/00_commitinfo_data.js';
declare global {
interface HTMLElementTagNameMap {
'idp-logincontainer': IdpLogincontainer;
}
}
@customElement('idp-logincontainer')
export class IdpLogincontainer extends DeesElement {
public static demo = () => html`<idp-logincontainer></idp-logincontainer>`;
@query('.loginPromptContainer')
loginPromptContainer: HTMLDivElement;
@query('.loginManagerContainer')
loginManagerContainer: HTMLDivElement
@query('.transferManagerContainer')
transferManagerContainer: HTMLDivElement
public receptionClient = new plugins.idpClient.IdpClient('https://reception.lossless.one:443', {
appUrl: 'https://sso.workspace.global/',
description: 'the central sso app for workspace.global',
logoUrl: 'https://assetbroker.lossless.one/some',
name: 'sso.workspace.global',
id: null,
});
constructor() {
super();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Sans';
position: absolute;
width: 100%;
height: 100%;
}
.mainContainer {
position: absolute;
top: 0px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: all 0.2s;
transition-delay: 0.2s;
transform: translate3d(0px, 20px, 0px);
pointer-events: none;
}
.loginPromptContainer.show {
opacity: 1;
pointer-events: all;
transform: translate3d(0px, 0px, 0px);
}
.loginManagerContainer.show {
opacity: 1;
pointer-events: all;
transform: translate3d(0px, 0px, 0px);
}
.transferManagerContainer.show {
opacity: 1;
pointer-events: all;
transform: translate3d(0px, 0px, 0px);
}
.loginblock {
max-width: 520px;
flex-grow: 1;
transform: translate3d(0px, 0px, 0px);
transition: all 0.2s;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
background: ${cssManager.bdTheme('#ffffff', '#181818')};
border-top: 1px solid ${cssManager.bdTheme('#ffffff', '#333333')};
border-radius: 16px;
overflow: hidden;
}
img {
width: 130px;
min-height: 34.9px;
display: block;
margin: auto;
margin-top: 16px;
margin-bottom: 25px;
filter: ${cssManager.bdTheme('invert(1)', '')};
}
.legalinfo {
text-align: center;
margin: auto;
margin-top: 10px;
color: ${cssManager.bdTheme('#666', '#ccc')};
font-size: 12px;
line-height: 100%;
padding: 8px;
background: ${cssManager.bdTheme('#f5f5f5', '#111')};
border-top: 1px solid ${cssManager.bdTheme('#ccc', '#222222')};
color: ${cssManager.bdTheme('#666', '#888')};
}
.legalinfo a {
color: ${cssManager.bdTheme('#666', '#ccc')};
text-decoration: none;
}
`,
];
render() {
return html`
<div class="mainContainer loginPromptContainer">
<div class="loginblock">
<img
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
/>
<idp-login></idp-login>
<div class="legalinfo">
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
| <a href="https://task.vc/" target="_blank">Company Website</a>
| <a href="https://support.task.vc/" target="_blank">Support</a>
| SSO v${commitinfo.version}
</div>
</div>
</div>
<div class="mainContainer loginManagerContainer">
<div class="loginblock">
<img
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
/>
<div class="legalinfo">
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
| <a href="https://task.vc/" target="_blank">Company Website</a>
| <a href="https://support.task.vc/" target="_blank">Support</a>
| SSO v${commitinfo.version}
</div>
</div>
</div>
<div class="mainContainer transferManagerContainer">
<div class="loginblock">
<img
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
/>
<idp-transfermanager></idp-transfermanager>
<div class="legalinfo">
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
| <a href="https://task.vc/" target="_blank">Company Website</a>
| <a href="https://support.task.vc/" target="_blank">Support</a>
| SSO v${commitinfo.version}
</div>
</div>
</div>
`;
}
public async showComponent(componentNameArg: 'loginPrompt' | 'loginManager' | 'transferManager') {
const domtoolsInstance = await this.domtoolsPromise;
const containerItems: HTMLDivElement[] = [
this.loginPromptContainer,
this.loginManagerContainer,
this.transferManagerContainer,
];
const show = async (itemArg: HTMLDivElement) => {
for (const containerItem of containerItems) {
if (containerItem !== itemArg) {
containerItem.classList.remove('show');
}
}
await domtoolsInstance.convenience.smartdelay.delayFor(200);
itemArg.classList.add('show');
await domtoolsInstance.convenience.smartdelay.delayFor(200);
};
switch (componentNameArg) {
case 'loginPrompt':
await show(this.loginPromptContainer);
break;
case 'loginManager':
await show(this.loginManagerContainer);
break;
case 'transferManager':
await show(this.transferManagerContainer);
break;
}
}
public async determineNextAction() {
const domtoolsInstance = await this.domtoolsPromise;
let action: plugins.idpInterfaces.data.TLoginAction;
if (domtoolsInstance.router.queryParams.getQueryParam('action')) {
action = domtoolsInstance.router.queryParams.getQueryParam('action');
}
if (window.location.pathname === '/afterregistration') {
await this.domtools.convenience.smartdelay.delayFor(1000);
await this.receptionClient.determineLoginStatus();
await this.receptionClient.getTransferTokenAndSwitchToLocation('https://account.workspace.global')
} else if (!(await this.receptionClient.determineLoginStatus()) && action === 'login') {
this.showComponent('loginPrompt');
} else if ((await this.receptionClient.determineLoginStatus()) && action === 'login') {
await this.showComponent('transferManager');
const wgTransferManager = this.shadowRoot.querySelector('idp-transfermanager');
await wgTransferManager.handleTransfer();
} else if ((await this.receptionClient.determineLoginStatus()) && action === 'manage') {
this.showComponent('loginManager');
} else if (action === 'logout') {
console.log('logging out, since requested action is "logout"');
await this.receptionClient.logout();
} else {
this.showComponent('loginPrompt');
}
}
public async firstUpdated() {
const domtoolsInstance = await this.domtoolsPromise;
await domtoolsInstance.convenience.smartdelay.delayFor(0);
console.log(`your are loggedin: ${await await this.receptionClient.determineLoginStatus()}`);
let appData: plugins.idpInterfaces.data.IApp;
if (domtoolsInstance.router.queryParams.getQueryParam('appdata')) {
appData = domtoolsInstance.convenience.smartjson.parseBase64(
domtoolsInstance.router.queryParams.getQueryParam('appdata')
);
}
const idpLogin = this.shadowRoot.querySelector('idp-login');
const idpTransferManager = this.shadowRoot.querySelector('idp-transfermanager');
idpLogin.appData = appData;
idpTransferManager.appData = appData;
await this.determineNextAction();
idpLogin.jwtObserable.subscribe({
next: async (jwtArg) => {
console.log('loggedIn');
await this.receptionClient.storeJwt(jwtArg);
await this.determineNextAction();
},
});
idpLogin.dispatchJwt();
}
}
+36 -121
View File
@@ -17,20 +17,17 @@ import '@uptime.link/webwidget';
import '@design.estate/dees-catalog';
import { DeesForm, DeesFormSubmit, DeesInputText } from '@design.estate/dees-catalog';
import { IdpState } from '../states/idp.state.js';
declare global {
interface HTMLElementTagNameMap {
'idp-login': IdpLogin;
'idp-loginprompt': IdpLoginPrompt;
}
}
@customElement('idp-login')
export class IdpLogin extends DeesElement {
public static demo = () => html`<idp-login></idp-login>`;
public static receptionUrl = 'https://reception.lossless.one/typedrequest';
@property()
public activePane: 'login' | 'register' = 'login';
@customElement('idp-loginprompt')
export class IdpLoginPrompt extends DeesElement {
public static demo = () => html`<idp-loginprompt></idp-loginprompt>`;
@property()
public productOfInterest: string;
@@ -60,56 +57,19 @@ export class IdpLogin extends DeesElement {
color: ${cssManager.bdTheme('#333333', '#ffffff')};
}
.box {
opacity: 0;
cursor: pointer;
overflow: hidden;
transition: all 0.2s ease;
height: 0px;
}
.box.active {
opacity: 1 !important;
height: 360px;
cursor: auto;
}
.loginbox {
}
.registerbox {
}
.boxcontent {
margin: 0px 20px;
}
.info {
text-align: center;
padding: 32px;
line-height: 1.5em;
font-size: 12px;
font-weight: 600;
color: #999;
}
.registerButton {
display: block;
transition: all 0.2s ease;
will-change: transform;
cursor: pointer;
}
.registerButton:hover {
color: #fff;
transform: scale(1.02);
margin-top: 16px;
}
`,
];
public render(): TemplateResult {
return html`
<div class="loginbox box ${this.activePane === 'login' ? 'active' : ''}">
<idp-centercontainer>
<div class="boxcontent">
<dees-form
id="loginForm"
@@ -133,37 +93,13 @@ export class IdpLogin extends DeesElement {
.isPasswordBool=${true}
></dees-input-text>
<dees-form-submit id="loginSubmitButton"></dees-form-submit>
<div class="info">
You'll go here: ${this.appData ? html`${this.appData.appUrl}` : html``}
<p><span class="registerButton" @click=${() => {this.activePane = 'register'}}>You can also register for a new account.</span></p>
</div>
</dees-form>
<dees-button type="discreet" class="registerButton" @clicked=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/register');
}}>Register instead</dees-button>
</div>
</div>
<div class="registerbox box ${this.activePane === 'register' ? 'active' : ''}">
<div class="boxcontent">
<dees-form
id="registrationForm"
@formData="${(eventArg) => {
this.register({
emailAddress: eventArg.detail.data.emailAddress,
});
}}"
>
<dees-input-text
.required=${true}
key="emailAddress"
label="Email-Address"
></dees-input-text>
<dees-input-checkbox .label="${'Agree to the Terms and Conditions'}"></dees-input-checkbox>
<dees-form-submit>Send Verification Email</dees-form-submit>
<div class="info">
Already have an account?
<p><span class="registerButton" @click=${() => {this.activePane = 'login'}}>Login instead.</span></p>
</div>
</dees-form>
</div>
</div>
</idp-centercontainer>
`;
}
@@ -174,7 +110,7 @@ export class IdpLogin extends DeesElement {
const loginSubmitButton: DeesFormSubmit = loginForm.querySelector('#loginSubmitButton');
const setButtonText = async () => {
if (loginPasswordInput.value) {
console.log('updating text of loginprompt.')
console.log('updating text of loginprompt.');
loginSubmitButton.text = 'Login';
} else {
loginSubmitButton.text = 'Send magic link (or enter password)';
@@ -188,16 +124,20 @@ export class IdpLogin extends DeesElement {
}
private login = async (valueArg: { emailAddress: string; passwordArg: string }) => {
// lets disable the register button
const registerButton: plugins.deesCatalog.DeesButton = this.shadowRoot.querySelector('.registerButton');
registerButton.disabled = true;
// lets define the needed requests
const idpState = await IdpState.getSingletonInstance();
const loginForm: DeesForm = this.shadowRoot.querySelector('#loginForm');
const loginRequestWithUsernameAndPassword =
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
IdpLogin.receptionUrl,
'/typedrequest',
'loginWithEmailOrUsernameAndPassword'
);
const loginRequestWithEmail =
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmail>(
IdpLogin.receptionUrl,
'/typedrequest',
'loginWithEmail'
);
@@ -218,9 +158,10 @@ export class IdpLogin extends DeesElement {
}
if (response.refreshToken) {
loginForm.setStatus('pending', 'obtained refreshToken...');
const jwt = await this.handleRefreshToken(response.refreshToken, 0);
const jwt = await idpState.idpClient.refreshJwt(response.refreshToken);
if (jwt) {
loginForm.setStatus('success', 'obtained jwt.');
idpState.domtools.router.pushUrl('/account');
} else {
loginForm.setStatus('error', 'something went wrong');
}
@@ -238,29 +179,6 @@ export class IdpLogin extends DeesElement {
}
};
private register = async (valueArg: { emailAddress: string }) => {
const registrationForm: DeesForm = this.shadowRoot.querySelector('#registrationForm');
registrationForm.setStatus('pending', 'registering...');
const firstSignupRequest =
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_FirstRegistration>(
IdpLogin.receptionUrl,
'firstRegistrationRequest'
);
const response = await firstSignupRequest
.fire({
email: valueArg.emailAddress,
productSlugOfInterest: this.productOfInterest,
})
.catch((err) => {
registrationForm.setStatus('error', err.message);
return null;
});
if (response.status === 'ok') {
registrationForm.setStatus('success', 'Please check your email!');
}
console.log(response);
};
public async dispatchJwt(jwtArg?: string) {
if (jwtArg !== undefined) {
console.log(`dispatching jwt from loginprompt.`);
@@ -277,24 +195,21 @@ export class IdpLogin extends DeesElement {
}
}
public async handleRefreshToken(refreshTokenArg: string, delayDispatchMillisArg = 0) {
// a refreshToken binds dierctly to a session.
// the refresh token is used on a continuous basis to get fresh and short-lived jwts
const refreshJwt = new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
IdpLogin.receptionUrl,
'refreshJwt'
);
const responseJwt = await refreshJwt.fire({
refreshToken: refreshTokenArg,
});
if (responseJwt.jwt) {
this.domtools.convenience.smartdelay.delayFor(delayDispatchMillisArg).then(() => {
this.dispatchJwt(responseJwt.jwt);
});
return responseJwt.jwt;
} else {
return null;
public async focus() {
(
this.shadowRoot.querySelector('#loginEmailInput') as plugins.deesCatalog.DeesInputText
).focus();
}
public async show() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.show();
}
public async hide() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.hide();
}
}
+209
View File
@@ -0,0 +1,209 @@
import * as plugins from '../plugins.js';
import {
customElement,
DeesElement,
property,
html,
type TemplateResult,
css,
cssManager,
state,
domtools,
} from '@design.estate/dees-element';
// third party catalogs
import '@uptime.link/webwidget';
import '@design.estate/dees-catalog';
import { DeesForm, DeesFormSubmit, DeesInputText } from '@design.estate/dees-catalog';
import { IdpState } from '../states/idp.state.js';
declare global {
interface HTMLElementTagNameMap {
'idp-registrationprompt': IdpRegistrationPrompt;
}
}
@customElement('idp-registrationprompt')
export class IdpRegistrationPrompt extends DeesElement {
public static demo = () => html`<idp-login></idp-login>`;
@property()
public productOfInterest: string;
@property()
jwt: string;
@property({
reflect: true,
type: Object,
})
appData: plugins.idpInterfaces.data.IApp;
public jwtObserable = new domtools.plugins.smartrx.rxjs.Subject<string>();
constructor() {
super();
domtools.elementBasic.setup();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Sans';
display: block;
color: ${cssManager.bdTheme('#333333', '#ffffff')};
}
.boxcontent {
margin: 0px 20px;
}
.registerButton {
display: block;
transition: all 0.2s ease;
will-change: transform;
cursor: pointer;
}
.registerButton:hover {
color: #fff;
transform: scale(1.02);
}
.loginButton {
margin-top: 16px;
}
`,
];
public render(): TemplateResult {
return html`
<idp-centercontainer>
<div class="boxcontent">
<dees-form
id="registrationForm"
@formData="${(eventArg) => {
this.register({
emailAddress: eventArg.detail.data.emailAddress,
});
}}"
>
<dees-input-text
.required=${true}
key="emailAddress"
label="Email-Address"
></dees-input-text>
<dees-input-checkbox
.label="${'Agree to the Terms and Conditions'}"
></dees-input-checkbox>
<dees-form-submit>Send Verification Email</dees-form-submit>
</dees-form>
<dees-button type="discreet" class="loginButton" @click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/login');
}}>Login instead</dees-button>
</div>
</idp-centercontainer>
`;
}
public async firstUpdated() {
await this.domtoolsPromise;
const idpState = await IdpState.getSingletonInstance();
const loggedIn = await idpState.idpClient.determineLoginStatus();
if (loggedIn) {
idpState.domtools.router.pushUrl('/');
}
const loginForm: DeesForm = this.shadowRoot.querySelector('#loginForm');
const loginPasswordInput: DeesInputText = loginForm.querySelector('#loginPasswordInput');
const loginSubmitButton: DeesFormSubmit = loginForm.querySelector('#loginSubmitButton');
const setButtonText = async () => {
if (loginPasswordInput.value) {
console.log('updating text of registrationprompt.');
loginSubmitButton.text = 'Login';
} else {
loginSubmitButton.text = 'Send magic link (or enter password)';
}
};
loginForm.changeSubject.subscribe(() => {
console.log(`checking button text ${loginPasswordInput.value}`);
setButtonText();
});
setButtonText();
}
private register = async (valueArg: { emailAddress: string }) => {
const registrationForm: DeesForm = this.shadowRoot.querySelector('#registrationForm');
registrationForm.setStatus('pending', 'registering...');
const firstSignupRequest =
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_FirstRegistration>(
'/typedrequest',
'firstRegistrationRequest'
);
const response = await firstSignupRequest
.fire({
email: valueArg.emailAddress,
productSlugOfInterest: this.productOfInterest,
})
.catch((err) => {
registrationForm.setStatus('error', err.message);
return null;
});
if (response.status === 'ok') {
registrationForm.setStatus('success', 'Please check your email!');
}
console.log(response);
};
public async dispatchJwt(jwtArg?: string) {
if (jwtArg !== undefined) {
console.log(`dispatching jwt from loginprompt.`);
this.jwt = jwtArg;
await domtools.plugins.smartdelay.delayFor(200);
this.dispatchEvent(
new CustomEvent('leleLoginGotJwt', {
detail: {
jwt: this.jwt,
},
})
);
this.jwtObserable.next(this.jwt);
}
}
public async handleRefreshToken(refreshTokenArg: string, delayDispatchMillisArg = 0) {
// a refreshToken binds directly to a session.
// the refresh token is used on a continuous basis to get fresh and short-lived jwts
const refreshJwt = new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
'/typedrequest',
'refreshJwt'
);
const responseJwt = await refreshJwt.fire({
refreshToken: refreshTokenArg,
});
if (responseJwt.jwt) {
this.domtools.convenience.smartdelay.delayFor(delayDispatchMillisArg).then(() => {
this.dispatchJwt(responseJwt.jwt);
});
return responseJwt.jwt;
} else {
return null;
}
}
public async show() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.show();
}
public async hide() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.hide();
}
}
+27 -38
View File
@@ -1,4 +1,4 @@
import { IdpState } from '../idp.state.js';
import { IdpState } from '../states/idp.state.js';
import * as plugins from '../plugins.js';
import {
customElement,
@@ -14,8 +14,6 @@ import {
@customElement('idp-registration-stepper')
export class IdpRegistrationStepper extends DeesElement {
public idpState = IdpState.getSingletonInstance();
@state()
private usedSubTemplate: TemplateResult;
@@ -66,44 +64,44 @@ export class IdpRegistrationStepper extends DeesElement {
}
public async firstUpdated() {
const idpState = await IdpState.getSingletonInstance();
await this.domtoolsPromise;
this.domtools.router.on(`/finishregistration`, async (routeArg) => {
this.storedData.validationTokenUrlParam = routeArg.queryParams.validationtoken;
const parsedUrl = plugins.smarturl.Smarturl.createFromUrl(window.location.href);
this.storedData.validationTokenUrlParam = parsedUrl.searchParams['validationtoken'];
console.log(`validationToken is ${this.storedData.validationTokenUrlParam}`);
if (!this.storedData.validationTokenUrlParam) {
this.usedSubTemplate = html`
You need a validation token, but we couldn't find one. Please contact workspace.global support.
You need a validation token, but we couldn't find one. Please contact workspace.global
support.
`;
await this.domtools.convenience.smartdelay.delayFor(5000);
this.usedSubTemplate = html` Redirecting you to workspace.global support... `;
await this.domtools.convenience.smartdelay.delayFor(2000);
window.location.href = 'https://support.workspace.global';
window.location.href = '/';
return;
}
// lets verify the info;
let tokenErrorMessage: string;
const resAfterRegEmailClicked =
await this.idpState.idpClient.requests.afterRegistrationEmailClicked
const resAfterRegEmailClicked = await idpState.idpClient.requests.afterRegistrationEmailClicked
.fire({
token: this.storedData.validationTokenUrlParam,
})
.catch(
(
err: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
err: (typeof DeesElement)['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
) => {
tokenErrorMessage = err.errorText;
return;
}
);
console.log(resAfterRegEmailClicked);
if (!resAfterRegEmailClicked || !resAfterRegEmailClicked.email) {
this.usedSubTemplate = html`
the supplied validation token does not match any registration sessions.<br />
${tokenErrorMessage ? html`Reason: ${tokenErrorMessage}` : null}
`;
await this.domtools.convenience.smartdelay.delayFor(5000);
this.usedSubTemplate = html`redirecting you for further support... `;
await this.domtools.convenience.smartdelay.delayFor(1000);
window.location.href = 'https://support.workspace.global';
idpState.domtools.router.pushUrl('/');
return;
} else {
this.storedData.email = resAfterRegEmailClicked.email;
@@ -130,7 +128,7 @@ export class IdpRegistrationStepper extends DeesElement {
validationFunc: async (stepperArg, elementArg) => {
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
const response = await this.idpState.idpClient.requests.setData
const response = await idpState.idpClient.requests.setData
.fire({
token: this.storedData.validationTokenUrlParam,
userData: {
@@ -143,7 +141,7 @@ export class IdpRegistrationStepper extends DeesElement {
})
.catch(
(
errArg: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
errArg: (typeof DeesElement)['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
) => {
deesForm.setStatus('error', errArg.errorText);
}
@@ -172,14 +170,14 @@ export class IdpRegistrationStepper extends DeesElement {
validationFunc: async (stepperArg, elementArg) => {
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
const response = await this.idpState.idpClient.requests.mobileNumberVerification
const response = await idpState.idpClient.requests.mobileNumberVerification
.fire({
token: this.storedData.validationTokenUrlParam,
mobileNumber: eventArg.detail.data.mobileNumber,
})
.catch(
(
errArg: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
errArg: (typeof DeesElement)['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
) => {
deesForm.setStatus('error', errArg.errorText);
}
@@ -208,7 +206,7 @@ export class IdpRegistrationStepper extends DeesElement {
validationFunc: async (stepperArg, elementArg) => {
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
const response = await this.idpState.idpClient.requests.mobileNumberVerification.fire({
const response = await idpState.idpClient.requests.mobileNumberVerification.fire({
token: this.storedData.validationTokenUrlParam,
verificationCode: eventArg.detail.data.verificationCode,
});
@@ -244,7 +242,7 @@ export class IdpRegistrationStepper extends DeesElement {
validationFunc: async (stepperArg, elementArg) => {
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
const response = await this.idpState.idpClient.requests.setData.fire({
const response = await idpState.idpClient.requests.setData.fire({
token: this.storedData.validationTokenUrlParam,
userData: {
username: null,
@@ -256,41 +254,32 @@ export class IdpRegistrationStepper extends DeesElement {
},
});
const finishRegistrationResponse =
await this.idpState.idpClient.requests.finishRegistration.fire({
await idpState.idpClient.requests.finishRegistration.fire({
token: this.storedData.validationTokenUrlParam,
});
deesForm.setStatus('pending', 'User created!');
await this.domtools.convenience.smartdelay.delayFor(500);
deesForm.setStatus('pending', 'Obtaining Refresh Token...');
const loginResponse = await this.idpState.idpClient.requests.loginWithUserNameAndPassword.fire(
{
const loginResponse =
await idpState.idpClient.requests.loginWithUserNameAndPassword.fire({
username: this.storedData.email,
password: eventArg.detail.data.password,
}
);
});
this.storedData.refreshToken = loginResponse.refreshToken;
deesForm.setStatus('pending', 'Obtaining JWT...');
const jwtResponse = await this.idpState.idpClient.requests.obtainJwt.fire({
const jwtResponse = await idpState.idpClient.requests.obtainJwt.fire({
refreshToken: this.storedData.refreshToken,
});
deesForm.setStatus('pending', 'Obtaining Transfer Token...');
await this.idpState.idpClient.setJwt(jwtResponse.jwt);
await this.idpState.idpClient.getTransferTokenAndSwitchToLocation('https://sso.workspace.global/afterregistration');
deesForm.setStatus('success', 'Ok! Lets Go!');
await idpState.idpClient.setJwt(jwtResponse.jwt);
idpState.domtools.router.pushUrl('/account');
});
},
},
] as plugins.deesCatalog.IStep[]}
></dees-stepper>`;
await this.domtools.convenience.smartdelay.delayFor(100);
});
this.domtools.router.on('/', async () => {
this.usedSubTemplate = html`Hm, this is app is not meant for what you are trying to do :) `;
await this.domtools.convenience.smartdelay.delayFor(2000);
this.usedSubTemplate = html`Redirecting you now...`;
window.location.href = `https://workspace.global`;
});
this.domtools.router._handleRouteState();
}
}
+75 -32
View File
@@ -9,9 +9,11 @@ import {
cssManager,
unsafeCSS,
css,
resolveExec,
type TemplateResult,
} from '@design.estate/dees-element';
import type { IdpViewcontainer } from '../views/viewcontainer.js';
import { IdpState } from '../states/idp.state.js';
@customElement('idp-welcome')
export class IdpWelcome extends DeesElement {
@@ -33,22 +35,20 @@ export class IdpWelcome extends DeesElement {
display: none;
}
h1 {
font-family: 'Cal Sans';
.maincontainer {
padding: 0px 16px;
}
.greeting {
text-align: center;
letter-spacing:0.0125em;
}
.textbox {
font-size: 16px;
font-weight: 600;
margin: 24px auto;
width: 500px;
background: #111111;
border-radius: 8px;
padding: 24px;
}
.textbox dees-button {
dees-button {
margin-top: 16px;
margin-bottom: 16px;
}
`,
];
@@ -56,27 +56,70 @@ export class IdpWelcome extends DeesElement {
public render(): TemplateResult {
return html`
<style></style>
<h1>idp.global</h1>
<div class="textbox">
Do you want to sign in or register?
<dees-button @click=${() => {
this.viewContainer.loadElement(elements.IdpLogincontainer);
}}>Sign In</dees-button>
<dees-button @click=${() => {}}>Register</dees-button>
</div>
<div class="textbox">
Do you want to use idp.global for your app?
<dees-button @click=${() => {}}>Open Developer Dashboard</dees-button>
</div>
<div class="textbox">
idp.global is a Open Source identity provider for the world wide web. You can get the code if you want to improve it.
<dees-button @click=${() => {
window.open('https://code.foss.global/idp.global/idp.global', '_blank');
}}>Get the code</dees-button>
</div>
<idp-centercontainer>
<div class="maincontainer">
${resolveExec(async () => {
const idpState = await IdpState.getSingletonInstance();
await idpState.idpClient.determineLoginStatus();
const data = await idpState.idpClient.whoIs().catch();
if (data?.user) {
return html`
<div class="greeting">Hello ${data.user.data.name}!</div>
<dees-button
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/account');
}}
>Manage your account</dees-button
>
<dees-button
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/logout');
}}
>Logout</dees-button
>
`;
}
return html`
Do you want to sign in or register?
<dees-button
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/login');
}}
>Sign In</dees-button
>
<dees-button
@click=${async () => {
const idpState = await IdpState.getSingletonInstance();
idpState.domtools.router.pushUrl('/register');
}}
>Register</dees-button
>
`;
})}
<dees-button @click=${() => {}}>Open Developer Dashboard</dees-button>
<dees-button
@click=${() => {
window.open('https://code.foss.global/idp.global/idp.global', '_blank');
}}
>Get the Source Code</dees-button
>
</div>
</idp-centercontainer>
`;
}
public async show() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.show();
}
public async hide() {
await this.updateComplete;
const centerContainer = this.shadowRoot.querySelector('idp-centercontainer');
await centerContainer.hide();
}
}
+6 -1
View File
@@ -1,5 +1,10 @@
export * from './idp-registration-stepper.js';
export * from './idp-logincontainer.js';
export * from './idp-centercontainer.js';
export * from './idp-loginprompt.js';
export * from './idp-registerprompt.js';
export * from './idp-transfermanager.js';
export * from './idp-welcome.js';
import { IdpAccountContent } from './account/index.js';
export { IdpAccountContent };
-17
View File
@@ -1,17 +0,0 @@
import * as plugins from './plugins.js';
export class IdpState {
// STATIC
public static getSingletonInstance() {
if (!this.instance) {
this.instance = new IdpState();
}
return this.instance;
}
private static instance: IdpState;
// INSTANCE
public receptionUrl = 'https://reception.lossless.one/typedrequest';
public idpClient = new plugins.idpClient.IdpClient(this.receptionUrl);
}
+1 -3
View File
@@ -44,7 +44,7 @@ const run = async () => {
},
});
const serviceWorker = await serviceworker.getServiceworkerClient();
// const serviceWorker = await serviceworker.getServiceworkerClient();
const mainTemplate = html`
<style>
@@ -58,8 +58,6 @@ const run = async () => {
render(mainTemplate, document.body);
const viewContainer: IdpViewcontainer = document.querySelector('idp-viewcontainer');
viewContainer.loadElement(IdpWelcome);
};
+13 -1
View File
@@ -13,6 +13,18 @@ export { typedrequest };
// @design.estate scope
import * as deesCatalog from '@design.estate/dees-catalog';
import * as deesDomtools from '@design.estate/dees-domtools';
import * as deesElement from '@design.estate/dees-element';
export { deesCatalog, deesElement };
export { deesCatalog, deesDomtools, deesElement };
// @push.rocks scope
import * as smartpromise from '@push.rocks/smartpromise';
import * as smarturl from '@push.rocks/smarturl';
export { smartpromise, smarturl };
// @tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };
+101
View File
@@ -0,0 +1,101 @@
import * as plugins from '../plugins.js';
import { IdpState } from './idp.state.js';
export type TStateTypes = 'IAccountState';
export interface IAccountState {
user: plugins.idpInterfaces.data.IUser;
/**
* the available orgs
*/
organizations: Array<plugins.idpInterfaces.data.IOrganization>;
roles: Array<plugins.idpInterfaces.data.IRole>
selectedOrg: plugins.idpInterfaces.data.IOrganization;
selectedOrgBillingPlan: plugins.tsclass.typeFest.PartialDeep<plugins.idpInterfaces.data.IBillingPlan>;
/**
* used for keeping the state when creating a new org
*/
newOrg: {
chosenName: string;
chosenSlug: string;
validated: boolean;
validationOk: boolean;
};
}
const smartStateInstance = new plugins.deesDomtools.plugins.smartstate.Smartstate<TStateTypes>();
export const accountState = await smartStateInstance.getStatePart<IAccountState>('IAccountState', {
user: null,
organizations: [],
roles: [],
selectedOrg: null,
selectedOrgBillingPlan: null,
newOrg: {
chosenName: null,
chosenSlug: null,
validated: null,
validationOk: null,
},
});
export const getOrganizationsAction = accountState.createAction<void>(
async (statePartArg, payloadArg) => {
const idpState = await IdpState.getSingletonInstance();
const currentState = statePartArg.getState();
const response = await idpState.idpClient.getRolesAndOrganizations();
currentState.organizations = response.organizations;
currentState.roles = response.roles;
return currentState;
}
);
export const setNewOrgName = accountState.createAction<string>(async (statePartArg, payloadArg) => {
const idpState = await IdpState.getSingletonInstance();
const currentState = statePartArg.getState();
currentState.newOrg.chosenName = payloadArg;
currentState.newOrg.chosenSlug = payloadArg
.replace(/[^a-zA-Z0-9]/g, '-')
.replace(/\s/g, '-')
.toLowerCase();
const result = await idpState.idpClient.createOrganization(
currentState.newOrg.chosenName,
currentState.newOrg.chosenSlug,
'checkAvailability'
);
console.log(result);
currentState.newOrg.validated = true;
currentState.newOrg.validationOk = result.nameAvailable;
if (payloadArg === '') {
currentState.newOrg.validated = false;
currentState.newOrg.validationOk = false;
}
return currentState;
});
export const manifestNewOrgName = accountState.createAction(async (statePartArg, payloadArg) => {
const idpState = await IdpState.getSingletonInstance();
const currentState: IAccountState = statePartArg.getState();
const result = await idpState.idpClient.createOrganization(
currentState.newOrg.chosenName,
currentState.newOrg.chosenSlug,
'manifest'
);
currentState.organizations.push(result.resultingOrganization);
currentState.selectedOrg = result.resultingOrganization;
return currentState;
});
export const setSelectedOrg = accountState.createAction<plugins.idpInterfaces.data.IOrganization>(async (statePartArg, payloadArg) => {
const currentState = statePartArg.getState();
currentState.selectedOrg = payloadArg;
return currentState;
})
export const updatePaddleCheckoutId = accountState.createAction<string>(async (statePartArg, checkoutIdArg) => {
const idpState = await IdpState.getSingletonInstance();
const currentState: IAccountState = statePartArg.getState();
const response = await idpState.idpClient.updatePaddleCheckoutId(currentState.selectedOrg.id, checkoutIdArg);
currentState.selectedOrgBillingPlan = response.billingPlan;
return currentState;
});
+78
View File
@@ -0,0 +1,78 @@
import * as plugins from '../plugins.js';
import { domtools } from '@design.estate/dees-element'
export class IdpState {
// STATIC
private static idpStateDeferred = plugins.smartpromise.defer<IdpState>();
public static async getSingletonInstance() {
if (!this.idpStateDeferred.claimed) {
this.idpStateDeferred.claim();
const newIdpState = new IdpState();
await newIdpState.init();
this.idpStateDeferred.resolve(newIdpState);
}
return this.idpStateDeferred.promise;
}
// INSTANCE
public receptionUrl = window.location.origin;
public idpClient = new plugins.idpClient.IdpClient(this.receptionUrl);
public domtools: domtools.DomTools;
public mainStatePart: plugins.deesDomtools.plugins.smartstate.StatePart<'main', {
view: 'welcome' | 'login' | 'register' | 'finishregistration' | 'account' | 'logout';
}>
public async init() {
this.idpClient.enableTypedSocket();
const domtoolsInstance = await domtools.DomTools.setupDomTools();
this.domtools = domtoolsInstance;
const state = new plugins.deesDomtools.plugins.smartstate.Smartstate<'main'>();
this.mainStatePart = await state.getStatePart('main', {
view: 'welcome',
}, 'soft');
this.domtools.router.on('/', async () => {
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'welcome',
})
});
this.domtools.router.on('/login', async () => {
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'login',
})
});
this.domtools.router.on('/logout', async () => {
await this.idpClient.logout();
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'logout',
})
});
this.domtools.router.on('/register', async () => {
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'register',
})
});
this.domtools.router.on('/finishregistration', async () => {
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'finishregistration',
})
});
this.domtools.router.on('/account{/*path}', async () => {
await this.mainStatePart.setState({
...this.mainStatePart.getState(),
view: 'account',
})
});
this.domtools.router._handleRouteState();
}
}
+51 -5
View File
@@ -1,4 +1,6 @@
import { IdpState } from '../states/idp.state.js';
import * as plugins from '../plugins.js';
import * as elements from '../elements/index.js';
import {
customElement,
@@ -32,21 +34,20 @@ export class IdpViewcontainer extends DeesElement {
min-width: 100vh;
min-height: 100vh;
}
`,
];
public render(): TemplateResult {
return html`
<style></style>
<div class="viewContainer">
</div>
<div class="viewContainer"></div>
`;
}
public currentElement: plugins.deesElement.DeesElement;
public async loadElement(viewElement: typeof plugins.deesElement.DeesElement) {
const idpState = await IdpState.getSingletonInstance();
// Wait until the viewContainer itself is rendered
await this.updateComplete;
@@ -58,20 +59,65 @@ export class IdpViewcontainer extends DeesElement {
throw new Error('View container not found in the rendered DOM.');
}
if (!this.currentElement) {
this.currentElement = viewContainer.children[0] as any;
}
// check if current element already is instance of viewElement
if (this.currentElement instanceof viewElement) {
return;
}
// Remove the current element if it exists
if (this.currentElement) {
const currentElement = this.currentElement as any;
if (currentElement.hide) {
await currentElement.hide();
}
viewContainer.removeChild(this.currentElement);
}
// Create a new instance of the viewElement
const newElement = new viewElement();
const newElement = new viewElement() as any;
(newElement as any).viewContainer = this;
viewContainer.appendChild(newElement);
if (newElement.show) {
await newElement.show();
}
// Wait until the new element is fully rendered
await newElement.updateComplete;
// Set the new element as the current element
this.currentElement = newElement;
}
public async firstUpdated() {
const idpState = await IdpState.getSingletonInstance();
idpState.mainStatePart
.select((stateArg) => stateArg.view)
.subscribe(async (viewArg) => {
switch (viewArg) {
case 'welcome':
await this.loadElement(elements.IdpWelcome);
break;
case 'login':
console.log('now on /login');
await this.loadElement(elements.IdpLoginPrompt);
break;
case 'register':
await this.loadElement(elements.IdpRegistrationPrompt);
break;
case 'finishregistration':
await this.loadElement(elements.IdpRegistrationStepper);
break;
case 'account':
console.log('now on /account');
await this.loadElement(elements.IdpAccountContent);
break;
}
});
}
}