@ibgib/web-gib 0.0.57 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/api/ibgib-api-bridge.d.mts +63 -1
- package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
- package/dist/api/ibgib-api-bridge.mjs +135 -0
- package/dist/api/ibgib-api-bridge.mjs.map +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts +22 -0
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
- package/dist/identity/custodian-delegate-helper.mjs +50 -0
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
- package/dist/identity/sso-popup-helper.d.mts +33 -0
- package/dist/identity/sso-popup-helper.d.mts.map +1 -0
- package/dist/identity/sso-popup-helper.mjs +124 -0
- package/dist/identity/sso-popup-helper.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
- package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +386 -63
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/package.json +4 -4
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/api/ibgib-api-bridge.mts +195 -1
- package/src/identity/custodian-delegate-helper.mts +61 -0
- package/src/identity/sso-popup-helper.mts +155 -0
- package/src/index.mts +3 -0
- package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
- package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
- package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +441 -67
- package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/sso-popup-helper
|
|
3
|
+
*
|
|
4
|
+
* DRY client helper for initiating OAuth2 SSO authentication via a popup window
|
|
5
|
+
* and capturing the returned authorization code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
9
|
+
import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
|
|
10
|
+
|
|
11
|
+
export interface SsoPopupOpts {
|
|
12
|
+
providerId: 'google' | 'github';
|
|
13
|
+
parentTjpAddr?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SsoPopupResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
code?: string;
|
|
19
|
+
stateObj?: any;
|
|
20
|
+
redirectUri?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Executes an OAuth2 SSO login/link authorization flow using a popup window.
|
|
26
|
+
*
|
|
27
|
+
* 1. Queries `GET /api/identity/sso/config` for safe public client configs.
|
|
28
|
+
* 2. Constructs the provider authorization URL with encoded state payload.
|
|
29
|
+
* 3. Opens a 600x700 popup window and listens for `sso-oauth-callback` postMessage.
|
|
30
|
+
* 4. Returns `{ success: true, code, stateObj, redirectUri }` upon authorization.
|
|
31
|
+
*/
|
|
32
|
+
export async function executeSsoOAuthPopup({
|
|
33
|
+
providerId,
|
|
34
|
+
parentTjpAddr,
|
|
35
|
+
}: SsoPopupOpts): Promise<SsoPopupResult> {
|
|
36
|
+
const lc = `[${executeSsoOAuthPopup.name}]`;
|
|
37
|
+
try {
|
|
38
|
+
// 1. Fetch public SSO configs from server
|
|
39
|
+
const apiBridge = getApiBridge();
|
|
40
|
+
const resConfig = await apiBridge.getSsoConfig();
|
|
41
|
+
if (!resConfig.success || !resConfig.providers) {
|
|
42
|
+
return { success: false, message: resConfig.message || 'SSO configuration retrieval failed.' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const providers = resConfig.providers as any;
|
|
46
|
+
const providerConfig = Array.isArray(providers)
|
|
47
|
+
? providers.find((p: any) => p.providerId === providerId)
|
|
48
|
+
: providers[providerId];
|
|
49
|
+
|
|
50
|
+
if (!providerConfig) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
message: `SSO provider '${providerId}' is not configured on this server environment.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Generate high-entropy user nonce & state
|
|
58
|
+
/**
|
|
59
|
+
* {@link getUUID} uses {@link globalThis.crypto.getRandomValues}
|
|
60
|
+
*/
|
|
61
|
+
const userNonce = await getUUID();
|
|
62
|
+
const stateObj = {
|
|
63
|
+
providerId,
|
|
64
|
+
parentTjpAddr,
|
|
65
|
+
userNonce
|
|
66
|
+
};
|
|
67
|
+
const state = btoa(JSON.stringify(stateObj));
|
|
68
|
+
|
|
69
|
+
// 3. Construct Auth URL if not pre-constructed
|
|
70
|
+
let authUrl = providerConfig.authUrl;
|
|
71
|
+
if (!authUrl.includes('client_id=')) {
|
|
72
|
+
authUrl = `${authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
|
|
73
|
+
} else if (!authUrl.includes('state=')) {
|
|
74
|
+
authUrl = `${authUrl}&state=${encodeURIComponent(state)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Open popup window
|
|
78
|
+
const popup = window.open(
|
|
79
|
+
authUrl,
|
|
80
|
+
'sso-oauth-popup',
|
|
81
|
+
'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!popup) {
|
|
85
|
+
return { success: false, message: 'Popup blocked by browser. Please enable popups for this site.' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 5. Promise wrapper listening for postMessage or window closure
|
|
89
|
+
return await new Promise<SsoPopupResult>((resolve) => {
|
|
90
|
+
let checkClosedInterval: any;
|
|
91
|
+
|
|
92
|
+
const messageListener = (event: MessageEvent) => {
|
|
93
|
+
if (event.origin !== window.location.origin) return;
|
|
94
|
+
const callbackData = event.data;
|
|
95
|
+
if (callbackData && callbackData.type === 'sso-oauth-callback') {
|
|
96
|
+
window.removeEventListener('message', messageListener);
|
|
97
|
+
clearInterval(checkClosedInterval);
|
|
98
|
+
resolve({
|
|
99
|
+
success: true,
|
|
100
|
+
code: callbackData.code,
|
|
101
|
+
stateObj: callbackData.stateObj || stateObj,
|
|
102
|
+
redirectUri: providerConfig.redirectUri
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
window.addEventListener('message', messageListener);
|
|
108
|
+
|
|
109
|
+
checkClosedInterval = setInterval(() => {
|
|
110
|
+
if (popup.closed) {
|
|
111
|
+
clearInterval(checkClosedInterval);
|
|
112
|
+
window.removeEventListener('message', messageListener);
|
|
113
|
+
resolve({ success: false, message: 'Authentication cancelled (window closed).' });
|
|
114
|
+
}
|
|
115
|
+
}, 500);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const emsg = extractErrorMsg(error);
|
|
120
|
+
console.error(`${lc} ${emsg}`);
|
|
121
|
+
return { success: false, message: emsg };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Checks if the current window was opened as an OAuth popup (`window.opener` exists)
|
|
127
|
+
* and has received an authorization code in the URL query string (`?code=...`).
|
|
128
|
+
* If so, posts `sso-oauth-callback` to the parent window and closes itself.
|
|
129
|
+
*/
|
|
130
|
+
export function checkAndNotifyPopupOpener(): boolean {
|
|
131
|
+
if (typeof window === 'undefined' || !window.opener) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const url = new URL(window.location.href);
|
|
136
|
+
const code = url.searchParams.get('code');
|
|
137
|
+
if (!code) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rawState = url.searchParams.get('state');
|
|
142
|
+
let stateObj: any;
|
|
143
|
+
if (rawState) {
|
|
144
|
+
try { stateObj = JSON.parse(atob(rawState)); } catch { }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
window.opener.postMessage({
|
|
148
|
+
type: 'sso-oauth-callback',
|
|
149
|
+
code,
|
|
150
|
+
stateObj
|
|
151
|
+
}, window.location.origin);
|
|
152
|
+
|
|
153
|
+
window.close();
|
|
154
|
+
return true;
|
|
155
|
+
}
|
package/src/index.mts
CHANGED
|
@@ -18,6 +18,9 @@ export * from "./api/ibgib-api-bridge.mjs";
|
|
|
18
18
|
|
|
19
19
|
// Identity & Sync services
|
|
20
20
|
export * from "./identity/keystone-sync-service.mjs";
|
|
21
|
+
export * from "./identity/custodian-delegate-helper.mjs";
|
|
22
|
+
// export * from "./identity/sso-callback-helper.mjs";
|
|
23
|
+
export * from "./identity/sso-popup-helper.mjs";
|
|
21
24
|
|
|
22
25
|
// Identity UI components
|
|
23
26
|
export * from "./ui/component/identity/index.mjs";
|
|
@@ -146,7 +146,8 @@ export class IdentityHeaderComponentInstance
|
|
|
146
146
|
};
|
|
147
147
|
this.keystoneCache.set(addr, details);
|
|
148
148
|
return details;
|
|
149
|
-
} catch {
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`${this.lc}[getKeystoneDetails] Registered keystone address ${addr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0002)`);
|
|
150
151
|
return { username: '', description: '', isPrimary: false };
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -10,17 +10,105 @@ h2 {
|
|
|
10
10
|
color: var(--clr-accent, #50fa7b);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
.mode-tabs {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: 0.5rem;
|
|
16
|
+
background: rgba(0, 0, 0, 0.25);
|
|
17
|
+
padding: 0.25rem;
|
|
18
|
+
border-radius: 10px;
|
|
19
|
+
border: 1px solid var(--clr-border, rgba(255, 255, 255, 0.1));
|
|
20
|
+
margin-bottom: 1rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.mode-tab {
|
|
24
|
+
flex: 1;
|
|
25
|
+
padding: 0.6rem 0.75rem;
|
|
26
|
+
border: none;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
background: transparent;
|
|
29
|
+
color: var(--clr-text-secondary, #a6adc8);
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
font-size: 0.9rem;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition: all 0.2s ease;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.mode-tab:hover {
|
|
37
|
+
color: var(--clr-text, #cdd6f4);
|
|
38
|
+
background: rgba(255, 255, 255, 0.05);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.mode-tab.active {
|
|
42
|
+
background: #45475a;
|
|
43
|
+
color: #89b4fa;
|
|
44
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
.subtitle {
|
|
14
48
|
color: var(--clr-text-secondary, #a6adc8);
|
|
15
|
-
font-size: 0.
|
|
49
|
+
font-size: 0.92rem;
|
|
16
50
|
margin: 0;
|
|
51
|
+
line-height: 1.45;
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
.content {
|
|
20
|
-
margin-top: 1.
|
|
55
|
+
margin-top: 1.25rem;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
gap: 1.25rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-panel.hidden, .hidden {
|
|
62
|
+
display: none !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sso-buttons {
|
|
21
66
|
display: flex;
|
|
22
67
|
flex-direction: column;
|
|
23
|
-
gap:
|
|
68
|
+
gap: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.sso-btn {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
gap: 0.75rem;
|
|
76
|
+
padding: 0.8rem 1.25rem;
|
|
77
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
background: #1e1e2e;
|
|
80
|
+
color: #cdd6f4;
|
|
81
|
+
font-size: 0.95rem;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: transform 0.15s ease, background 0.15s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sso-btn:hover {
|
|
88
|
+
transform: translateY(-1px);
|
|
89
|
+
background: #313244;
|
|
90
|
+
border-color: #89b4fa;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.divider {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
text-align: center;
|
|
97
|
+
margin: 0.5rem 0;
|
|
98
|
+
color: #a6adc8;
|
|
99
|
+
font-size: 0.75rem;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
letter-spacing: 1px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.divider::before, .divider::after {
|
|
105
|
+
content: '';
|
|
106
|
+
flex: 1;
|
|
107
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.divider span {
|
|
111
|
+
padding: 0 0.75rem;
|
|
24
112
|
}
|
|
25
113
|
|
|
26
114
|
.input-group {
|
|
@@ -34,8 +122,25 @@ h2 {
|
|
|
34
122
|
color: var(--clr-text-secondary, #a6adc8);
|
|
35
123
|
}
|
|
36
124
|
|
|
125
|
+
.text-input {
|
|
126
|
+
width: 100%;
|
|
127
|
+
padding: 0.75rem 1rem;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
130
|
+
background: #11111b;
|
|
131
|
+
color: #cdd6f4;
|
|
132
|
+
font-size: 0.95rem;
|
|
133
|
+
box-sizing: border-box;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.text-input:focus {
|
|
137
|
+
outline: none;
|
|
138
|
+
border-color: #89b4fa;
|
|
139
|
+
box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2);
|
|
140
|
+
}
|
|
141
|
+
|
|
37
142
|
.action-row {
|
|
38
|
-
margin-top:
|
|
143
|
+
margin-top: 0.5rem;
|
|
39
144
|
width: 100%;
|
|
40
145
|
}
|
|
41
146
|
|
|
@@ -65,10 +170,6 @@ h2 {
|
|
|
65
170
|
margin-top: 0.5rem;
|
|
66
171
|
}
|
|
67
172
|
|
|
68
|
-
.status-area.hidden {
|
|
69
|
-
display: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
173
|
.result-area {
|
|
73
174
|
padding: 1.5rem;
|
|
74
175
|
background: rgba(255, 255, 255, 0.03);
|
|
@@ -77,7 +178,6 @@ h2 {
|
|
|
77
178
|
display: flex;
|
|
78
179
|
flex-direction: column;
|
|
79
180
|
gap: 0.75rem;
|
|
80
|
-
transition: opacity var(--t-med, 0.2s ease);
|
|
81
181
|
}
|
|
82
182
|
|
|
83
183
|
.result-area.collapsed {
|
|
@@ -99,18 +199,4 @@ h2 {
|
|
|
99
199
|
input::placeholder {
|
|
100
200
|
color: rgba(255, 255, 255, 0.35);
|
|
101
201
|
opacity: 1;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.section-code-verification {
|
|
105
|
-
margin-top: 1.5rem;
|
|
106
|
-
padding: 1.25rem;
|
|
107
|
-
border: 2px dashed #f59e0b;
|
|
108
|
-
background: rgba(245, 158, 11, 0.05);
|
|
109
|
-
box-shadow: 0 0 15px rgba(245, 158, 11, 0.2);
|
|
110
|
-
border-radius: 12px;
|
|
111
|
-
transition: all 0.3s ease;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.section-code-verification.hidden {
|
|
115
|
-
display: none;
|
|
116
202
|
}
|
|
@@ -1,61 +1,123 @@
|
|
|
1
|
-
<div id="container" class="component-container">
|
|
2
|
-
<h2 id="title">Create Account</h2>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
<div id="container" class="component-container">
|
|
2
|
+
<h2 id="title">Create Account</h2>
|
|
3
|
+
|
|
4
|
+
<!-- Mode Selector Tabs -->
|
|
5
|
+
<div class="mode-tabs">
|
|
6
|
+
<button id="tab-quick" class="mode-tab active">⚡ Quick (Passwordless)</button>
|
|
7
|
+
<button id="tab-password" class="mode-tab">🔑 Password-based</button>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- Mode Subtitles -->
|
|
11
|
+
<p id="subtitle-quick" class="subtitle">⚡ Skip passwords — sign in with Google, GitHub, or an Email code. You can always set up a password later.</p>
|
|
12
|
+
<p id="subtitle-password" class="subtitle hidden">🔑 Full local ownership — your password never leaves your device. You can link Google or GitHub later.</p>
|
|
13
|
+
|
|
14
|
+
<div class="content">
|
|
15
|
+
<!-- ----------------------------------------------------------------- -->
|
|
16
|
+
<!-- Tab 1: Quick (Passwordless) Content -->
|
|
17
|
+
<!-- ----------------------------------------------------------------- -->
|
|
18
|
+
<div id="panel-quick" class="tab-panel">
|
|
19
|
+
<div class="sso-buttons">
|
|
20
|
+
<button id="btn-google-sso" class="sso-btn google-btn">
|
|
21
|
+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z"/></svg>
|
|
22
|
+
Continue with Google
|
|
23
|
+
</button>
|
|
24
|
+
<button id="btn-github-sso" class="sso-btn github-btn">
|
|
25
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
26
|
+
Continue with GitHub
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="divider">
|
|
31
|
+
<span>OR USE EMAIL CODE</span>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="input-group">
|
|
35
|
+
<div style="display: flex; align-items: center; gap: 0.4rem;">
|
|
36
|
+
<label for="input-quick-email">Use custom email sign-in</label>
|
|
37
|
+
<button id="btn-email-info" class="info-icon-btn" title="Learn about custom email sign-in" style="background: none; border: none; cursor: pointer; padding: 0; font-size: 0.9rem; line-height: 1;">ℹ️</button>
|
|
38
|
+
</div>
|
|
39
|
+
<input type="email" id="input-quick-email" placeholder="Enter your email address..." class="text-input">
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="action-row" style="margin-top: 0.75rem;">
|
|
43
|
+
<button id="btn-quick-send-code" class="action-btn">Send 6-Digit Verification Code</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div id="section-quick-otp" class="input-group hidden" style="margin-top: 1.25rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
|
|
47
|
+
<label for="input-quick-otp" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
|
|
48
|
+
<p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">Check your email for your 6-digit verification code:</p>
|
|
49
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
|
|
50
|
+
<input type="text" id="input-quick-otp" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
|
|
51
|
+
<button id="btn-quick-verify-code" class="action-btn" style="white-space: nowrap; width: auto;">Verify Code</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- ----------------------------------------------------------------- -->
|
|
57
|
+
<!-- Tab 2: Password-based Content -->
|
|
58
|
+
<!-- ----------------------------------------------------------------- -->
|
|
59
|
+
<div id="panel-password" class="tab-panel hidden">
|
|
60
|
+
<div class="input-group">
|
|
61
|
+
<label for="input-username">Username</label>
|
|
62
|
+
<input type="text" id="input-username" placeholder="Enter your username..." class="text-input">
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="input-group">
|
|
66
|
+
<label for="input-description">Description (Optional)</label>
|
|
67
|
+
<input type="text" id="input-description" placeholder="Enter an optional description..." class="text-input">
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="input-group">
|
|
71
|
+
<label for="input-email">Primary Email (Optional)</label>
|
|
72
|
+
<input type="email" id="input-email" placeholder="Enter your primary email address..." class="text-input">
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="input-group">
|
|
76
|
+
<label for="input-secret">Password (Keep this private!)</label>
|
|
77
|
+
<input type="password" id="input-secret" placeholder="Enter your password..." class="secret-input text-input">
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="checkbox-group" style="margin-top: 0.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
|
|
81
|
+
<input type="checkbox" id="checkbox-make-active" checked style="cursor: pointer; width: 1rem; height: 1rem;">
|
|
82
|
+
<label for="checkbox-make-active" style="cursor: pointer; font-size: 0.9rem; user-select: none;">Make this the active identity</label>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="action-row">
|
|
86
|
+
<button id="btn-generate" class="action-btn">Generate Identity Keystone</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- ----------------------------------------------------------------- -->
|
|
91
|
+
<!-- Status & Verification Areas -->
|
|
92
|
+
<!-- ----------------------------------------------------------------- -->
|
|
93
|
+
<div id="status-area" class="status-area hidden">
|
|
94
|
+
<p id="status-text" class="status-msg"></p>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div id="section-code-verification" class="input-group hidden" style="margin-top: 1.5rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
|
|
98
|
+
<label for="input-verify-code" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
|
|
99
|
+
<p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">A 6-digit verification code was sent to your email. Enter it below to activate your identity:</p>
|
|
100
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
|
|
101
|
+
<input type="text" id="input-verify-code" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
|
|
102
|
+
<button id="btn-submit-code" class="action-btn" style="white-space: nowrap;">Verify Code</button>
|
|
103
|
+
<button id="btn-resend-code" class="action-btn" style="white-space: nowrap; background: #45475a; color: #cdd6f4;">Resend Code</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div id="result-area" class="result-area collapsed">
|
|
108
|
+
<h3>New Keystone Generated!</h3>
|
|
109
|
+
<p class="address-label">Address:</p>
|
|
110
|
+
<code id="keystone-addr" class="keystone-addr"></code>
|
|
111
|
+
<p class="warning">⚠️ Store this address safely. It is your root identity for this space.</p>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
|
|
115
|
+
Already have a device paired? <button id="btn-switch-sign-in" class="link-btn" style="background: none; border: none; color: #89b4fa; font-weight: 600; cursor: pointer; text-decoration: underline; padding: 0; font-size: 0.9rem;">Pair New Device</button>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Privacy Footer Note -->
|
|
119
|
+
<div class="privacy-footer" style="margin-top: 1rem; text-align: center; font-size: 0.78rem; color: #a6adc8; line-height: 1.4;">
|
|
120
|
+
ℹ️ Primary email addresses and identity keystones form part of the public graph for account discovery. Business data requires authorized sync delegates. Learn more in our <a id="link-privacy-policy" href="#privacy" style="color: #89b4fa; text-decoration: underline;">Privacy Policy</a>.
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
61
123
|
</div>
|