igniteui-cli 15.2.2 → 15.3.1-beta.1
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/lib/commands/build.js +7 -12
- package/package.json +7 -7
- package/templates/blazor/igb/projects/ai-config/files/skills/AGENTS.md +0 -5
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/SKILL.md +2 -0
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/charts.md +7 -35
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/data-display.md +0 -54
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/feedback.md +0 -38
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/form-controls.md +0 -68
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout-manager.md +1 -124
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout.md +0 -62
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-grids/references/grid-migration.md +322 -0
- package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-theming/SKILL.md +1 -1
- package/templates/react/igr-ts/projects/_base/files/package.json +1 -0
- package/templates/react/igr-ts/projects/_base/files/src/app/app.tsx +4 -2
- package/templates/react/igr-ts/projects/_base/files/src/setupTests.ts +12 -0
- package/templates/react/igr-ts/projects/_base/files/styles.css +6 -0
- package/templates/react/igr-ts/projects/_base_with_home/files/index.html +2 -1
- package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/home.tsx +60 -10
- package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css +79 -20
- package/templates/react/igr-ts/projects/ai-config/files/skills/grid-lite-to-igr-grid-migration/SKILL.md +274 -0
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/SKILL.md +0 -8
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/CHARTS-GRIDS.md +6 -36
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/COMPONENT-CATALOGUE.md +8 -142
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/EVENT-HANDLING.md +2 -0
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/SKILL.md +7 -14
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/CSS-THEMING.md +2 -0
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/MCP-SERVER.md +0 -8
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/SKILL.md +2 -2
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/reference/component-mapping.md +60 -74
- package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/reference/gotchas.md +2 -2
- package/templates/react/igr-ts/projects/empty/index.js +2 -2
- package/templates/react/igr-ts/projects/side-nav/files/src/app/app-routes.tsx +5 -0
- package/templates/react/igr-ts/projects/side-nav/files/src/app/app.css +82 -0
- package/templates/react/igr-ts/projects/side-nav/files/src/app/app.tsx +104 -0
- package/templates/react/igr-ts/projects/side-nav/files/src/app/home/home.tsx +69 -0
- package/templates/react/igr-ts/projects/side-nav/files/src/app/home/style.module.css +105 -0
- package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.d.ts +2 -2
- package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.js +7 -7
- package/templates/react/igr-ts/projects/side-nav-auth/files/index.html +19 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app-routes.tsx +24 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.css +84 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.tsx +124 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthContext.tsx +73 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthGuard.tsx +14 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.module.css +93 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.tsx +69 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.module.css +42 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.tsx +44 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.module.css +14 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.tsx +49 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.module.css +74 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.tsx +67 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.module.css +87 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.tsx +42 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectFacebook.tsx +44 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectGoogle.tsx +40 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectMicrosoft.tsx +40 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
- package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
- package/templates/react/igr-ts/projects/side-nav-auth/index.d.ts +15 -0
- package/templates/react/igr-ts/projects/side-nav-auth/index.js +46 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app-routes.tsx +5 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.css +109 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.test.tsx +20 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.tsx +81 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/home.tsx +69 -0
- package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/style.module.css +105 -0
- package/templates/react/igr-ts/projects/side-nav-mini/index.d.ts +15 -0
- package/templates/react/igr-ts/projects/side-nav-mini/index.js +46 -0
- package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.css +106 -0
- package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.tsx +101 -0
- package/templates/react/igr-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
- package/templates/react/igr-ts/projects/side-nav-mini-auth/index.js +50 -0
- package/templates/webcomponents/igc-ts/projects/_base/files/src/app/app.ts +6 -1
- package/templates/webcomponents/igc-ts/projects/_base/files/styles.css +1 -0
- package/templates/webcomponents/igc-ts/projects/_base_with_home/files/index.html +2 -0
- package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/app/home/home.ts +103 -9
- package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/assets/wc.png +0 -0
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-choose-components/SKILL.md +122 -160
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/SKILL.md +83 -311
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/references/mcp-setup.md +69 -0
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/SKILL.md +4 -1
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/component-mapping.md +60 -61
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/gotchas.md +15 -11
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-migrate-grid-lite-to-premium/SKILL.md +446 -0
- package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-optimize-bundle-size/SKILL.md +23 -274
- package/templates/webcomponents/igc-ts/projects/empty/index.js +1 -1
- package/templates/webcomponents/igc-ts/projects/side-nav/files/index.html +21 -0
- package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts +9 -0
- package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts +192 -22
- package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts +175 -0
- package/templates/webcomponents/igc-ts/projects/side-nav/index.js +1 -1
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/index.html +25 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app-routing.ts +37 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app.ts +251 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-bar/login-bar.ts +124 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-dialog/login-dialog.ts +253 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/profile/profile.ts +142 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-facebook.ts +57 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-google.ts +53 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-microsoft.ts +53 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.d.ts +15 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.js +46 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app-routing.ts +13 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app.ts +238 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.d.ts +14 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.js +45 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/files/src/app/app.ts +258 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
- package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.js +50 -0
- package/templates/react/igr-ts/projects/top-nav/files/src/app/app.css +0 -62
- package/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx +0 -18
- package/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx +0 -19
- /package/templates/react/igr-ts/projects/{top-nav → side-nav}/files/src/app/app.test.tsx +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
3
|
+
import { Router } from '@vaadin/router';
|
|
4
|
+
import { defineComponents, IgcButtonComponent, IgcDialogComponent, IgcIconComponent, IgcInputComponent } from 'igniteui-webcomponents';
|
|
5
|
+
import { Authentication } from '../services/authentication.js';
|
|
6
|
+
import { ExternalAuth } from '../services/externalAuth.js';
|
|
7
|
+
import { UserStore } from '../services/userStore.js';
|
|
8
|
+
import type { User } from '../models/user.js';
|
|
9
|
+
|
|
10
|
+
defineComponents(IgcButtonComponent, IgcDialogComponent, IgcIconComponent, IgcInputComponent);
|
|
11
|
+
|
|
12
|
+
@customElement('auth-login-dialog')
|
|
13
|
+
export class LoginDialogElement extends LitElement {
|
|
14
|
+
@state() private showLogin = true;
|
|
15
|
+
@state() private error = '';
|
|
16
|
+
@state() private _loginValid = false;
|
|
17
|
+
@state() private _registerValid = false;
|
|
18
|
+
|
|
19
|
+
static styles = css`
|
|
20
|
+
igc-dialog::part(base) {
|
|
21
|
+
max-width: 24rem;
|
|
22
|
+
width: calc(100vw - 48px);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
igc-dialog::part(title) {
|
|
26
|
+
font-size: 1.125rem;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
color: #2d2d2d;
|
|
29
|
+
border-bottom: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.form {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-flow: column;
|
|
35
|
+
gap: 16px;
|
|
36
|
+
padding: 8px 0 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.form > * {
|
|
40
|
+
width: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
igc-input {
|
|
44
|
+
--ig-input-group-focused-secondary-color: #239ef0;
|
|
45
|
+
--ig-input-group-focused-border-color: #239ef0;
|
|
46
|
+
--ig-input-group-filled-text-color: #2d2d2d;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
igc-input igc-icon {
|
|
50
|
+
color: #0075d2;
|
|
51
|
+
--ig-icon-size: 1.50rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.error {
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: .875rem;
|
|
57
|
+
color: #d32f2f;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.submit-btn {
|
|
61
|
+
display: block;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.submit-btn::part(base) {
|
|
65
|
+
width: 100%;
|
|
66
|
+
min-height: 40px;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.submit-btn:not([disabled])::part(base) {
|
|
72
|
+
background: #239ef0;
|
|
73
|
+
color: #fff;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.submit-btn:not([disabled])::part(base):hover {
|
|
77
|
+
background: #1a8fd8;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.submit-btn[disabled]::part(base) {
|
|
81
|
+
background: #e0e0e0;
|
|
82
|
+
color: #767676;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.link-btn {
|
|
86
|
+
align-self: center;
|
|
87
|
+
text-align: center;
|
|
88
|
+
color: #0075d2;
|
|
89
|
+
font-size: .875rem;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
text-decoration: underline;
|
|
92
|
+
text-transform: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.link-btn:hover,
|
|
96
|
+
.link-btn:focus-visible {
|
|
97
|
+
color: #005da8;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.social-login {
|
|
101
|
+
display: grid;
|
|
102
|
+
gap: 8px;
|
|
103
|
+
padding-top: 16px;
|
|
104
|
+
border-top: 1px solid #d7d7d7;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.social-btn {
|
|
108
|
+
display: block;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.social-btn::part(base) {
|
|
112
|
+
width: 100%;
|
|
113
|
+
min-height: 40px;
|
|
114
|
+
color: #fff;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.google::part(base) { background: rgb(255, 19, 74); }
|
|
120
|
+
.facebook::part(base) { background: rgb(19, 119, 213); }
|
|
121
|
+
.microsoft::part(base) { background: rgb(27, 158, 245); }
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
private dialogRef: IgcDialogComponent | null = null;
|
|
125
|
+
|
|
126
|
+
public open() {
|
|
127
|
+
this.showLogin = true;
|
|
128
|
+
this.error = '';
|
|
129
|
+
this.dialogRef?.show();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
firstUpdated() {
|
|
133
|
+
this.dialogRef = this.shadowRoot?.querySelector('igc-dialog') ?? null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private checkLoginValidity = (e: Event) => {
|
|
137
|
+
this._loginValid = (e.currentTarget as HTMLFormElement).checkValidity();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
private checkRegisterValidity = (e: Event) => {
|
|
141
|
+
this._registerValid = (e.currentTarget as HTMLFormElement).checkValidity();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
private handleLoginSubmit = async (e: Event) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
this.error = '';
|
|
147
|
+
const form = e.target as HTMLFormElement;
|
|
148
|
+
const data = new FormData(form);
|
|
149
|
+
const result = await Authentication.login({
|
|
150
|
+
email: data.get('email') as string,
|
|
151
|
+
password: data.get('password') as string,
|
|
152
|
+
});
|
|
153
|
+
if (result.user) {
|
|
154
|
+
form.reset();
|
|
155
|
+
this._loginValid = false;
|
|
156
|
+
UserStore.setUser(result.user as User);
|
|
157
|
+
this.dialogRef?.hide();
|
|
158
|
+
this.dispatchEvent(new CustomEvent('auth-change', { bubbles: true, composed: true }));
|
|
159
|
+
Router.go('/auth/profile');
|
|
160
|
+
} else {
|
|
161
|
+
this.error = result.error ?? 'Login failed';
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
private handleRegisterSubmit = async (e: Event) => {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
this.error = '';
|
|
168
|
+
const form = e.target as HTMLFormElement;
|
|
169
|
+
const data = new FormData(form);
|
|
170
|
+
const result = await Authentication.register({
|
|
171
|
+
given_name: data.get('given_name') as string,
|
|
172
|
+
family_name: data.get('family_name') as string,
|
|
173
|
+
email: data.get('email') as string,
|
|
174
|
+
password: data.get('password') as string,
|
|
175
|
+
});
|
|
176
|
+
if (result.user) {
|
|
177
|
+
form.reset();
|
|
178
|
+
this._registerValid = false;
|
|
179
|
+
UserStore.setUser(result.user as User);
|
|
180
|
+
this.dialogRef?.hide();
|
|
181
|
+
this.dispatchEvent(new CustomEvent('auth-change', { bubbles: true, composed: true }));
|
|
182
|
+
Router.go('/auth/profile');
|
|
183
|
+
} else {
|
|
184
|
+
this.error = result.error ?? 'Registration failed';
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
render() {
|
|
189
|
+
const title = this.showLogin ? 'Login' : 'Register';
|
|
190
|
+
|
|
191
|
+
const loginForm = html`
|
|
192
|
+
<form class="form" @submit=${this.handleLoginSubmit} @input=${this.checkLoginValidity} novalidate>
|
|
193
|
+
<igc-input outlined type="email" name="email" label="Email" autocomplete="email" required>
|
|
194
|
+
<igc-icon slot="suffix" name="account_circle" collection="material"></igc-icon>
|
|
195
|
+
</igc-input>
|
|
196
|
+
<igc-input outlined type="password" name="password" label="Password" autocomplete="current-password" required>
|
|
197
|
+
<igc-icon slot="suffix" name="lock" collection="material"></igc-icon>
|
|
198
|
+
</igc-input>
|
|
199
|
+
${this.error ? html`<p class="error">${this.error}</p>` : ''}
|
|
200
|
+
<igc-button class="submit-btn" variant="contained" type="submit" ?disabled=${!this._loginValid}>Log In</igc-button>
|
|
201
|
+
<a class="link-btn" @click=${() => { this.showLogin = false; this.error = ''; }} role="button" tabindex="0">Create new account</a>
|
|
202
|
+
${ExternalAuth.hasProvider() ? html`
|
|
203
|
+
<div class="social-login">
|
|
204
|
+
${ExternalAuth.hasProvider('google') ? html`
|
|
205
|
+
<igc-button class="social-btn google" variant="contained" type="button"
|
|
206
|
+
@click=${() => ExternalAuth.login('google')}>Sign in with Google</igc-button>
|
|
207
|
+
` : ''}
|
|
208
|
+
${ExternalAuth.hasProvider('facebook') ? html`
|
|
209
|
+
<igc-button class="social-btn facebook" variant="contained" type="button"
|
|
210
|
+
@click=${() => ExternalAuth.login('facebook')}>Sign in with Facebook</igc-button>
|
|
211
|
+
` : ''}
|
|
212
|
+
${ExternalAuth.hasProvider('microsoft') ? html`
|
|
213
|
+
<igc-button class="social-btn microsoft" variant="contained" type="button"
|
|
214
|
+
@click=${() => ExternalAuth.login('microsoft')}>Sign in with Microsoft</igc-button>
|
|
215
|
+
` : ''}
|
|
216
|
+
</div>
|
|
217
|
+
` : ''}
|
|
218
|
+
</form>
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
const registerForm = html`
|
|
222
|
+
<form class="form" @submit=${this.handleRegisterSubmit} @input=${this.checkRegisterValidity} novalidate>
|
|
223
|
+
<igc-input outlined type="text" name="given_name" label="First Name" autocomplete="given-name" required>
|
|
224
|
+
<igc-icon slot="suffix" name="assignment_ind" collection="material"></igc-icon>
|
|
225
|
+
</igc-input>
|
|
226
|
+
<igc-input outlined type="text" name="family_name" label="Last Name" autocomplete="family-name">
|
|
227
|
+
<igc-icon slot="suffix" name="assignment_ind" collection="material"></igc-icon>
|
|
228
|
+
</igc-input>
|
|
229
|
+
<igc-input outlined type="email" name="email" label="Email" autocomplete="email" required>
|
|
230
|
+
<igc-icon slot="suffix" name="account_circle" collection="material"></igc-icon>
|
|
231
|
+
</igc-input>
|
|
232
|
+
<igc-input outlined type="password" name="password" label="Password" autocomplete="new-password" required>
|
|
233
|
+
<igc-icon slot="suffix" name="lock" collection="material"></igc-icon>
|
|
234
|
+
</igc-input>
|
|
235
|
+
${this.error ? html`<p class="error">${this.error}</p>` : ''}
|
|
236
|
+
<igc-button class="submit-btn" variant="contained" type="submit" ?disabled=${!this._registerValid}>Sign Up</igc-button>
|
|
237
|
+
<a class="link-btn" @click=${() => { this.showLogin = true; this.error = ''; }} role="button" tabindex="0">Have an account?</a>
|
|
238
|
+
</form>
|
|
239
|
+
`;
|
|
240
|
+
return html`
|
|
241
|
+
<igc-dialog .title=${title} .closeOnOutsideClick=${true}>
|
|
242
|
+
<span slot="footer"></span>
|
|
243
|
+
${this.showLogin ? loginForm : registerForm}
|
|
244
|
+
</igc-dialog>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
declare global {
|
|
250
|
+
interface HTMLElementTagNameMap {
|
|
251
|
+
'auth-login-dialog': LoginDialogElement;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** User profile returned by a social (external) auth provider. */
|
|
2
|
+
export interface ExternalLogin {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
email?: string; // not always present use id as fallback key
|
|
6
|
+
given_name?: string;
|
|
7
|
+
family_name?: string;
|
|
8
|
+
picture?: string;
|
|
9
|
+
externalToken: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Data transfer model expected from backend API JWT-s */
|
|
2
|
+
export interface UserJWT {
|
|
3
|
+
exp: number;
|
|
4
|
+
name: string;
|
|
5
|
+
given_name: string;
|
|
6
|
+
family_name: string;
|
|
7
|
+
email: string;
|
|
8
|
+
picture?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Client user model */
|
|
12
|
+
export interface User extends UserJWT {
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LoginResult {
|
|
17
|
+
user?: User;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Login } from '../models/login.js';
|
|
2
|
+
import type { RegisterInfo } from '../models/register-info.js';
|
|
3
|
+
import type { ExternalLogin } from '../models/external-login.js';
|
|
4
|
+
import type { LoginResult } from '../models/user.js';
|
|
5
|
+
import { parseUser } from './jwtUtil.js';
|
|
6
|
+
import { fakeLogin, fakeRegister, fakeExtLogin } from './fakeBackend.js';
|
|
7
|
+
|
|
8
|
+
/** Authentication service — swap fakeLogin/fakeRegister for real API calls when ready. */
|
|
9
|
+
export const Authentication = {
|
|
10
|
+
async login(data: Login): Promise<LoginResult> {
|
|
11
|
+
try {
|
|
12
|
+
const token = await fakeLogin(data);
|
|
13
|
+
return { user: parseUser(token) };
|
|
14
|
+
} catch (e: any) {
|
|
15
|
+
return { error: e.message };
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async register(data: RegisterInfo): Promise<LoginResult> {
|
|
20
|
+
try {
|
|
21
|
+
const token = await fakeRegister(data);
|
|
22
|
+
return { user: parseUser(token) };
|
|
23
|
+
} catch (e: any) {
|
|
24
|
+
return { error: e.message };
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/** Send user info from a social provider to the external login endpoint. */
|
|
29
|
+
async loginWith(data: ExternalLogin): Promise<LoginResult> {
|
|
30
|
+
try {
|
|
31
|
+
const token = fakeExtLogin(data);
|
|
32
|
+
return { user: parseUser(token) };
|
|
33
|
+
} catch (e: any) {
|
|
34
|
+
return { error: e.message };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Social login configuration.
|
|
2
|
+
// To enable a provider, set its entry in oauthConfig below with your real credentials
|
|
3
|
+
// from the provider's developer console.
|
|
4
|
+
//
|
|
5
|
+
// Redirect URIs to register in each provider's app settings:
|
|
6
|
+
// {your-origin}/auth/redirect-google
|
|
7
|
+
// {your-origin}/auth/redirect-facebook
|
|
8
|
+
// {your-origin}/auth/redirect-microsoft
|
|
9
|
+
//
|
|
10
|
+
// Developer consoles:
|
|
11
|
+
// Google: https://console.cloud.google.com/apis/credentials
|
|
12
|
+
// Microsoft: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps
|
|
13
|
+
// Facebook: https://developers.facebook.com/apps
|
|
14
|
+
|
|
15
|
+
export type OAuthProvider = 'google' | 'facebook' | 'microsoft';
|
|
16
|
+
|
|
17
|
+
export interface OAuthConfig {
|
|
18
|
+
google?: { clientId: string };
|
|
19
|
+
|
|
20
|
+
// tenantId defaults to 'common' (multi-tenant). Set it for single-tenant apps.
|
|
21
|
+
// IMPORTANT: The redirect URI must be registered as a SPA redirect URI in Azure
|
|
22
|
+
// (not "Web"), otherwise the token exchange will fail with a CORS error.
|
|
23
|
+
// See: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
|
|
24
|
+
microsoft?: { clientId: string; tenantId?: string };
|
|
25
|
+
|
|
26
|
+
// Facebook login uses the JS SDK (popup flow). The SDK script must be loaded in
|
|
27
|
+
// index.html (see below). In the Facebook app dashboard you must also:
|
|
28
|
+
// - Enable "Login with the JavaScript SDK"
|
|
29
|
+
// - Add your domain to "Allowed Domains for the JavaScript SDK"
|
|
30
|
+
// - Add the redirect URI to "Valid OAuth Redirect URIs"
|
|
31
|
+
// - Serve the app over HTTPS
|
|
32
|
+
// See: https://developers.facebook.com/docs/facebook-login/web
|
|
33
|
+
facebook?: { clientId: string };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Active OAuth configuration — fill in the providers you want to enable, for example:
|
|
37
|
+
//
|
|
38
|
+
// export const oauthConfig: OAuthConfig = {
|
|
39
|
+
// google: { clientId: 'YOUR_GOOGLE_CLIENT_ID' },
|
|
40
|
+
// microsoft: { clientId: 'YOUR_AZURE_APP_CLIENT_ID', tenantId: 'common' },
|
|
41
|
+
// // Note: Facebook requires HTTPS even for local dev - use ngrok or a local SSL proxy.
|
|
42
|
+
// facebook: { clientId: 'YOUR_FACEBOOK_APP_ID' },
|
|
43
|
+
// };
|
|
44
|
+
export const oauthConfig: OAuthConfig = {};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { ExternalLogin } from '../models/external-login.js';
|
|
2
|
+
import type { OAuthProvider } from './external-auth-config.js';
|
|
3
|
+
import { oauthConfig } from './external-auth-config.js';
|
|
4
|
+
import { generateCodeVerifier, generateCodeChallenge, buildAuthUrl } from './pkce.js';
|
|
5
|
+
|
|
6
|
+
// sessionStorage keys
|
|
7
|
+
const VERIFIER_KEY = '_pkce_verifier';
|
|
8
|
+
const STATE_KEY = '_oauth_state';
|
|
9
|
+
const FB_USER_KEY = '_fb_user';
|
|
10
|
+
const ACTIVE_PROVIDER_KEY = '_ext_active_provider';
|
|
11
|
+
|
|
12
|
+
// Declared by the Facebook JS SDK (loaded via script tag in index.html)
|
|
13
|
+
declare const FB: any;
|
|
14
|
+
|
|
15
|
+
// Set to true once FB.init() has been called in this session.
|
|
16
|
+
// Prevents FB.logout() from being called before initialization.
|
|
17
|
+
let fbInitialized = false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decode a JWT payload segment. Handles Base64URL encoding (no padding, - and _ chars)
|
|
21
|
+
* which `atob()` does not accept natively - missing padding causes `InvalidCharacterError`.
|
|
22
|
+
*/
|
|
23
|
+
function decodeJwtPayload(token: string): any {
|
|
24
|
+
const base64url = token.split('.')[1];
|
|
25
|
+
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
|
26
|
+
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '=');
|
|
27
|
+
return JSON.parse(atob(padded));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Waits until the Facebook JS SDK has loaded and is available on window.
|
|
32
|
+
* The SDK is loaded with `async defer` so it may not be ready when login() is called.
|
|
33
|
+
*/
|
|
34
|
+
function waitForFB(): Promise<void> {
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
if (typeof (window as any).FB !== 'undefined') { resolve(); return; }
|
|
37
|
+
const id = setInterval(() => {
|
|
38
|
+
if (typeof (window as any).FB !== 'undefined') { clearInterval(id); resolve(); }
|
|
39
|
+
}, 50);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* External (social) authentication service.
|
|
45
|
+
* Supports Google and Microsoft via OIDC/PKCE, and Facebook via the JS SDK.
|
|
46
|
+
*
|
|
47
|
+
* Usage: call login(provider) to start the flow; call handleRedirect(provider)
|
|
48
|
+
* on the matching redirect page to complete it and retrieve the user profile.
|
|
49
|
+
*/
|
|
50
|
+
export const ExternalAuth = {
|
|
51
|
+
/** Returns true if any provider (or the specific provider) is configured. */
|
|
52
|
+
hasProvider(provider?: OAuthProvider): boolean {
|
|
53
|
+
if (provider) {
|
|
54
|
+
return provider in oauthConfig && (oauthConfig as any)[provider] != null;
|
|
55
|
+
}
|
|
56
|
+
return Object.values(oauthConfig).some(v => v != null);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/** Initiate login for the given provider. Redirects the page to the provider's auth endpoint. */
|
|
60
|
+
async login(provider: OAuthProvider): Promise<void> {
|
|
61
|
+
localStorage.setItem(ACTIVE_PROVIDER_KEY, provider);
|
|
62
|
+
if (provider === 'google') {
|
|
63
|
+
const cfg = oauthConfig.google!;
|
|
64
|
+
const verifier = generateCodeVerifier();
|
|
65
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
66
|
+
sessionStorage.setItem(VERIFIER_KEY, verifier);
|
|
67
|
+
const state = crypto.randomUUID();
|
|
68
|
+
sessionStorage.setItem(STATE_KEY, state);
|
|
69
|
+
const redirectUri = `${window.location.origin}/auth/redirect-google`;
|
|
70
|
+
window.location.href = buildAuthUrl('https://accounts.google.com/o/oauth2/v2/auth', {
|
|
71
|
+
response_type: 'code',
|
|
72
|
+
client_id: cfg.clientId,
|
|
73
|
+
redirect_uri: redirectUri,
|
|
74
|
+
scope: 'openid profile email',
|
|
75
|
+
code_challenge: challenge,
|
|
76
|
+
code_challenge_method: 'S256',
|
|
77
|
+
state,
|
|
78
|
+
});
|
|
79
|
+
} else if (provider === 'microsoft') {
|
|
80
|
+
const cfg = oauthConfig.microsoft!;
|
|
81
|
+
const tenantId = cfg.tenantId ?? 'common';
|
|
82
|
+
const verifier = generateCodeVerifier();
|
|
83
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
84
|
+
sessionStorage.setItem(VERIFIER_KEY, verifier);
|
|
85
|
+
const state = crypto.randomUUID();
|
|
86
|
+
sessionStorage.setItem(STATE_KEY, state);
|
|
87
|
+
const redirectUri = `${window.location.origin}/auth/redirect-microsoft`;
|
|
88
|
+
window.location.href = buildAuthUrl(
|
|
89
|
+
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
90
|
+
{
|
|
91
|
+
response_type: 'code',
|
|
92
|
+
client_id: cfg.clientId,
|
|
93
|
+
redirect_uri: redirectUri,
|
|
94
|
+
scope: 'openid profile email',
|
|
95
|
+
code_challenge: challenge,
|
|
96
|
+
code_challenge_method: 'S256',
|
|
97
|
+
state,
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
} else if (provider === 'facebook') {
|
|
101
|
+
const cfg = oauthConfig.facebook!;
|
|
102
|
+
// Wait for the SDK to load (it is included with `async defer` in index.html
|
|
103
|
+
// and may not be available yet when the user clicks the login button).
|
|
104
|
+
await waitForFB();
|
|
105
|
+
FB.init({ appId: cfg.clientId, xfbml: false, version: 'v3.1' });
|
|
106
|
+
fbInitialized = true;
|
|
107
|
+
FB.login(
|
|
108
|
+
(response: any) => {
|
|
109
|
+
if (response.authResponse) {
|
|
110
|
+
FB.api(
|
|
111
|
+
'/me?fields=id,email,name,first_name,last_name,picture',
|
|
112
|
+
(res: any) => {
|
|
113
|
+
const user: ExternalLogin = {
|
|
114
|
+
id: res.id,
|
|
115
|
+
name: res.name,
|
|
116
|
+
given_name: res.first_name,
|
|
117
|
+
family_name: res.last_name,
|
|
118
|
+
email: res.email,
|
|
119
|
+
// Facebook returns picture as an object: { data: { url, width, height } }
|
|
120
|
+
picture: res.picture?.data?.url,
|
|
121
|
+
externalToken: FB.getAuthResponse()?.accessToken ?? '',
|
|
122
|
+
};
|
|
123
|
+
sessionStorage.setItem(FB_USER_KEY, JSON.stringify(user));
|
|
124
|
+
window.location.href = '/auth/redirect-facebook';
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{ scope: 'public_profile,email' }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Complete the OAuth redirect flow and return the external user profile.
|
|
136
|
+
* Call this from the /auth/redirect-{provider} page.
|
|
137
|
+
*
|
|
138
|
+
* For Google/Microsoft: exchanges the authorization code (PKCE) for tokens.
|
|
139
|
+
* For Facebook: reads the profile stored during the FB.login() popup flow.
|
|
140
|
+
*/
|
|
141
|
+
async handleRedirect(provider: OAuthProvider): Promise<ExternalLogin> {
|
|
142
|
+
if (provider === 'facebook') {
|
|
143
|
+
const stored = sessionStorage.getItem(FB_USER_KEY);
|
|
144
|
+
if (!stored) throw new Error('No Facebook user data found. Please try again.');
|
|
145
|
+
sessionStorage.removeItem(FB_USER_KEY);
|
|
146
|
+
return JSON.parse(stored) as ExternalLogin;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const params = new URLSearchParams(window.location.search);
|
|
150
|
+
const code = params.get('code');
|
|
151
|
+
if (!code) throw new Error('Missing authorization code in redirect URL.');
|
|
152
|
+
|
|
153
|
+
// Validate the state parameter to prevent CSRF attacks.
|
|
154
|
+
const returnedState = params.get('state');
|
|
155
|
+
const savedState = sessionStorage.getItem(STATE_KEY);
|
|
156
|
+
sessionStorage.removeItem(STATE_KEY);
|
|
157
|
+
if (!returnedState || returnedState !== savedState) {
|
|
158
|
+
throw new Error('OAuth state mismatch. The request may have been tampered with.');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const verifier = sessionStorage.getItem(VERIFIER_KEY);
|
|
162
|
+
if (!verifier) throw new Error('Missing PKCE code verifier. Please try again.');
|
|
163
|
+
sessionStorage.removeItem(VERIFIER_KEY);
|
|
164
|
+
|
|
165
|
+
if (provider === 'google') {
|
|
166
|
+
const cfg = oauthConfig.google!;
|
|
167
|
+
const redirectUri = `${window.location.origin}/auth/redirect-google`;
|
|
168
|
+
const res = await fetch('https://oauth2.googleapis.com/token', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
171
|
+
body: new URLSearchParams({
|
|
172
|
+
grant_type: 'authorization_code',
|
|
173
|
+
client_id: cfg.clientId,
|
|
174
|
+
redirect_uri: redirectUri,
|
|
175
|
+
code,
|
|
176
|
+
code_verifier: verifier,
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) throw new Error('Google token exchange failed.');
|
|
180
|
+
const data = await res.json();
|
|
181
|
+
// Decode the id_token to extract user claims - no extra userinfo request needed
|
|
182
|
+
const payload = decodeJwtPayload(data.id_token);
|
|
183
|
+
return {
|
|
184
|
+
id: payload.sub,
|
|
185
|
+
name: payload.name,
|
|
186
|
+
given_name: payload.given_name,
|
|
187
|
+
family_name: payload.family_name,
|
|
188
|
+
email: payload.email,
|
|
189
|
+
picture: payload.picture,
|
|
190
|
+
externalToken: data.access_token,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (provider === 'microsoft') {
|
|
195
|
+
const cfg = oauthConfig.microsoft!;
|
|
196
|
+
const tenantId = cfg.tenantId ?? 'common';
|
|
197
|
+
const redirectUri = `${window.location.origin}/auth/redirect-microsoft`;
|
|
198
|
+
const res = await fetch(
|
|
199
|
+
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
|
|
200
|
+
{
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
203
|
+
body: new URLSearchParams({
|
|
204
|
+
grant_type: 'authorization_code',
|
|
205
|
+
client_id: cfg.clientId,
|
|
206
|
+
redirect_uri: redirectUri,
|
|
207
|
+
code,
|
|
208
|
+
code_verifier: verifier,
|
|
209
|
+
}),
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
if (!res.ok) throw new Error('Microsoft token exchange failed.');
|
|
213
|
+
const data = await res.json();
|
|
214
|
+
const payload = decodeJwtPayload(data.id_token);
|
|
215
|
+
return {
|
|
216
|
+
id: payload.oid ?? payload.sub,
|
|
217
|
+
name: payload.name,
|
|
218
|
+
email: payload.email ?? payload.preferred_username,
|
|
219
|
+
externalToken: data.access_token,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Sign out from the active external provider (if any) and clear its stored state.
|
|
228
|
+
* Call this alongside clearing local user state on logout.
|
|
229
|
+
*/
|
|
230
|
+
logout(): void {
|
|
231
|
+
const provider = localStorage.getItem(ACTIVE_PROVIDER_KEY) as OAuthProvider | null;
|
|
232
|
+
localStorage.removeItem(ACTIVE_PROVIDER_KEY);
|
|
233
|
+
sessionStorage.removeItem(VERIFIER_KEY);
|
|
234
|
+
sessionStorage.removeItem(FB_USER_KEY);
|
|
235
|
+
|
|
236
|
+
if (!provider) return;
|
|
237
|
+
|
|
238
|
+
if (provider === 'google') {
|
|
239
|
+
// Redirect to Google's end-session endpoint to clear the Google session.
|
|
240
|
+
// The user is returned to the app root after sign-out.
|
|
241
|
+
const cfg = oauthConfig.google;
|
|
242
|
+
if (cfg) {
|
|
243
|
+
window.location.href = `https://accounts.google.com/logout`;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (provider === 'microsoft') {
|
|
249
|
+
const cfg = oauthConfig.microsoft;
|
|
250
|
+
if (cfg) {
|
|
251
|
+
const tenantId = cfg.tenantId ?? 'common';
|
|
252
|
+
const postLogoutRedirectUri = encodeURIComponent(window.location.origin);
|
|
253
|
+
window.location.href =
|
|
254
|
+
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/logout` +
|
|
255
|
+
`?post_logout_redirect_uri=${postLogoutRedirectUri}`;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (provider === 'facebook') {
|
|
261
|
+
// Only call FB.logout() when the SDK was initialised in this session.
|
|
262
|
+
// Calling it on a fresh page load (before FB.init) throws an error.
|
|
263
|
+
try {
|
|
264
|
+
if (fbInitialized && typeof FB !== 'undefined') {
|
|
265
|
+
FB.logout();
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// SDK not loaded - nothing to do
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
};
|