@kengcom202/kcore-ui 0.4.0 → 0.5.0

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.
@@ -5,6 +5,7 @@ import { FormBuilder, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Valid
5
5
  import { HttpClient, HttpErrorResponse } from "@angular/common/http";
6
6
  import { ActivatedRoute, Router, RouterLink } from "@angular/router";
7
7
  import { catchError, throwError } from "rxjs";
8
+ import { toSignal } from "@angular/core/rxjs-interop";
8
9
  var AnchoredPopup = class {
9
10
  anchor;
10
11
  component;
@@ -1395,7 +1396,8 @@ const KC_AUTH_CONFIG = new InjectionToken("KC_AUTH_CONFIG");
1395
1396
  const KC_AUTH_PATHS = {
1396
1397
  login: "/login",
1397
1398
  register: "/register",
1398
- ssoCallback: "/sso-callback"
1399
+ ssoCallback: "/sso-callback",
1400
+ thaidCallback: "/thaid-callback"
1399
1401
  };
1400
1402
  function resolveKcAuthConfig(o) {
1401
1403
  return {
@@ -1404,7 +1406,8 @@ function resolveKcAuthConfig(o) {
1404
1406
  apiBase: (o.apiBase ?? "/api").replace(/\/+$/, ""),
1405
1407
  homeUrl: o.homeUrl || "/",
1406
1408
  sso: o.sso ? { label: o.sso.label || "SSO" } : null,
1407
- passwordLogin: o.passwordLogin !== false || !o.sso,
1409
+ thaid: o.thaid ? { label: o.thaid.label || "ThaiD" } : null,
1410
+ passwordLogin: o.passwordLogin !== false || !o.sso && !o.thaid,
1408
1411
  register: o.register ?? "none",
1409
1412
  orgLabel: o.orgLabel || "หน่วยงาน",
1410
1413
  adminRoles: o.adminRoles ?? ["admin"],
@@ -1418,6 +1421,12 @@ function provideKcAuth(options) {
1418
1421
  useValue: resolveKcAuthConfig(options)
1419
1422
  }]);
1420
1423
  }
1424
+ const KC_LOGIN_LOCK_SECONDS = 300;
1425
+ function kcLoginRetryAfter(err) {
1426
+ if (!(err instanceof HttpErrorResponse) || err.status !== 429) return 0;
1427
+ const s = Number(err.error?.retryAfter);
1428
+ return Number.isFinite(s) && s > 0 ? Math.ceil(s) : 300;
1429
+ }
1421
1430
  function kcErrorMessage(err, fallback) {
1422
1431
  if (err instanceof HttpErrorResponse) {
1423
1432
  const m = err.error?.message;
@@ -1429,6 +1438,7 @@ function kcErrorMessage(err, fallback) {
1429
1438
  const SESSION_KEY = "kc_session";
1430
1439
  const KC_SSO_STATE_KEY = "kc_sso_state";
1431
1440
  const KC_SSO_TICKET_KEY = "kc_sso_ticket";
1441
+ const KC_THAID_STATE_KEY = "kc_thaid_state";
1432
1442
  const KC_RETURN_URL_KEY = "kc_return_url";
1433
1443
  function kcTokenExpired(token, now = Date.now()) {
1434
1444
  const part = token.split(".")[1];
@@ -1492,6 +1502,7 @@ var KcAuthService = class KcAuthService {
1492
1502
  for (const k of [
1493
1503
  KC_SSO_STATE_KEY,
1494
1504
  KC_SSO_TICKET_KEY,
1505
+ KC_THAID_STATE_KEY,
1495
1506
  KC_RETURN_URL_KEY
1496
1507
  ]) this.storage("sessionStorage")?.removeItem(k);
1497
1508
  }
@@ -1719,6 +1730,9 @@ const AUTH_CSS = `
1719
1730
  .kc-auth-field-error { color: var(--kc-auth-danger, #c62828); font-size: 0.86em; }
1720
1731
  .kc-auth-hint { margin: 2px 0 0; color: var(--kc-auth-muted, #806f80); font-size: 0.86em; }
1721
1732
  .kc-auth-check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
1733
+ .kc-auth-check:has(input:disabled) { cursor: not-allowed; opacity: 0.7; }
1734
+ .kc-auth-methods { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; }
1735
+ .kc-auth-methods > .kc-auth-btn { margin-left: auto; min-width: 110px; }
1722
1736
 
1723
1737
  /* ---- ปุ่ม ---- */
1724
1738
  .kc-auth-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-top: 6px; }
@@ -1760,7 +1774,7 @@ const AUTH_CSS = `
1760
1774
  .kc-auth-btn-row { display: flex; gap: 10px; margin-top: 6px; }
1761
1775
  .kc-auth-btn-row > .kc-auth-btn { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
1762
1776
  /* ปิดรหัสผ่าน: ปุ่ม SSO ปุ่มเดียวตรงกลาง */
1763
- .kc-auth-sso-only { display: flex; justify-content: center; padding: 8px 0 4px; }
1777
+ .kc-auth-sso-only { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 8px 0 4px; }
1764
1778
  .kc-auth-sso-only > .kc-auth-btn { min-width: 75%; height: 46px; padding: 0 24px; font-size: 1.05em; }
1765
1779
  .kc-auth-btn.block { display: flex; width: 100%; }
1766
1780
  .kc-auth-btn.sm { height: 30px; padding: 0 10px; font-size: 0.93em; }
@@ -1981,6 +1995,23 @@ i0.ɵɵngDeclareClassMetadata({
1981
1995
  }],
1982
1996
  ctorParameters: () => []
1983
1997
  });
1998
+ const KC_LOGIN_LOCK_KEY = "kc_login_lock";
1999
+ const lockKey = (username) => username.trim().toLowerCase();
2000
+ function readLocks(storage) {
2001
+ try {
2002
+ const raw = JSON.parse(storage?.getItem("kc_login_lock") ?? "{}");
2003
+ const now = Date.now();
2004
+ return Object.fromEntries(Object.entries(raw && typeof raw === "object" ? raw : {}).filter((e) => typeof e[1] === "number" && e[1] > now));
2005
+ } catch {
2006
+ return {};
2007
+ }
2008
+ }
2009
+ function writeLocks(storage, locks) {
2010
+ try {
2011
+ if (Object.keys(locks).length) storage?.setItem(KC_LOGIN_LOCK_KEY, JSON.stringify(locks));
2012
+ else storage?.removeItem(KC_LOGIN_LOCK_KEY);
2013
+ } catch {}
2014
+ }
1984
2015
  var KcLoginComponent = class KcLoginComponent {
1985
2016
  config = inject(KC_AUTH_CONFIG);
1986
2017
  paths = KC_AUTH_PATHS;
@@ -1988,19 +2019,57 @@ var KcLoginComponent = class KcLoginComponent {
1988
2019
  http = inject(HttpClient);
1989
2020
  router = inject(Router);
1990
2021
  route = inject(ActivatedRoute);
2022
+ doc = inject(DOCUMENT);
1991
2023
  busy = signal("", ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
2024
+ methods = signal({
2025
+ password: true,
2026
+ sso: true,
2027
+ thaid: true
2028
+ }, ...ngDevMode ? [{ debugName: "methods" }] : /* istanbul ignore next */ []);
2029
+ external = computed(() => [...this.config.sso && this.methods().sso ? [{
2030
+ key: "sso",
2031
+ label: this.config.sso.label,
2032
+ stateKey: KC_SSO_STATE_KEY
2033
+ }] : [], ...this.config.thaid && this.methods().thaid ? [{
2034
+ key: "thaid",
2035
+ label: this.config.thaid.label,
2036
+ stateKey: KC_THAID_STATE_KEY
2037
+ }] : []], ...ngDevMode ? [{ debugName: "external" }] : /* istanbul ignore next */ []);
2038
+ showPassword = computed(() => this.config.passwordLogin && this.methods().password || !this.external().length, ...ngDevMode ? [{ debugName: "showPassword" }] : /* istanbul ignore next */ []);
1992
2039
  message = signal(null, ...ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []);
1993
2040
  form = inject(FormBuilder).nonNullable.group({
1994
2041
  username: ["", Validators.required],
1995
2042
  password: ["", Validators.required]
1996
2043
  });
2044
+ locks = signal(readLocks(this.storage()), ...ngDevMode ? [{ debugName: "locks" }] : /* istanbul ignore next */ []);
2045
+ now = signal(Date.now(), ...ngDevMode ? [{ debugName: "now" }] : /* istanbul ignore next */ []);
2046
+ username = toSignal(this.form.controls.username.valueChanges, { initialValue: "" });
2047
+ lockLeft = computed(() => {
2048
+ const until = this.locks()[lockKey(this.username())] ?? 0;
2049
+ return Math.max(0, Math.ceil((until - this.now()) / 1e3));
2050
+ }, ...ngDevMode ? [{ debugName: "lockLeft" }] : /* istanbul ignore next */ []);
2051
+ lockText = computed(() => {
2052
+ const s = this.lockLeft();
2053
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
2054
+ }, ...ngDevMode ? [{ debugName: "lockText" }] : /* istanbul ignore next */ []);
2055
+ timer;
1997
2056
  constructor() {
1998
2057
  ensureAuthStyles();
2058
+ inject(DestroyRef).onDestroy(() => this.stopTimer());
2059
+ if (Object.keys(this.locks()).length) this.startTimer();
1999
2060
  }
2000
2061
  get returnUrl() {
2001
2062
  return this.auth.safeReturnUrl(this.route.snapshot.queryParamMap.get("returnUrl"));
2002
2063
  }
2003
2064
  ngOnInit() {
2065
+ this.http.get(`${this.config.apiBase}/user/login-methods`).subscribe({
2066
+ next: (m) => this.methods.set({
2067
+ password: m?.password !== false,
2068
+ sso: m?.sso !== false,
2069
+ thaid: m?.thaid !== false
2070
+ }),
2071
+ error: () => {}
2072
+ });
2004
2073
  if (this.route.snapshot.queryParamMap.get("expired") === "1") this.message.set({
2005
2074
  text: "หมดเวลาการใช้งาน กรุณาเข้าสู่ระบบใหม่",
2006
2075
  error: false
@@ -2008,7 +2077,7 @@ var KcLoginComponent = class KcLoginComponent {
2008
2077
  else if (this.auth.isLoggedIn()) this.router.navigateByUrl(this.returnUrl);
2009
2078
  }
2010
2079
  submit() {
2011
- if (this.busy()) return;
2080
+ if (this.busy() || this.lockLeft()) return;
2012
2081
  const v = this.form.getRawValue();
2013
2082
  if (!v.username.trim() || !v.password) {
2014
2083
  this.message.set({
@@ -2024,33 +2093,66 @@ var KcLoginComponent = class KcLoginComponent {
2024
2093
  password: v.password
2025
2094
  }).subscribe({
2026
2095
  next: (res) => {
2096
+ this.setLock(v.username, 0);
2027
2097
  this.auth.start(res);
2028
2098
  this.router.navigateByUrl(this.returnUrl);
2029
2099
  },
2030
2100
  error: (err) => {
2031
2101
  this.busy.set("");
2032
- this.message.set({
2102
+ const wait = kcLoginRetryAfter(err);
2103
+ if (wait) this.setLock(v.username, Date.now() + wait * 1e3);
2104
+ else this.message.set({
2033
2105
  text: kcErrorMessage(err, "เข้าสู่ระบบไม่สำเร็จ"),
2034
2106
  error: true
2035
2107
  });
2036
2108
  }
2037
2109
  });
2038
2110
  }
2039
- startSso() {
2040
- const sso = this.config.sso;
2041
- if (!sso || this.busy()) return;
2042
- this.busy.set("sso");
2111
+ setLock(username, until) {
2112
+ const next = { ...this.locks() };
2113
+ if (until) next[lockKey(username)] = until;
2114
+ else delete next[lockKey(username)];
2115
+ this.locks.set(next);
2116
+ this.now.set(Date.now());
2117
+ writeLocks(this.storage(), next);
2118
+ if (until) this.startTimer();
2119
+ }
2120
+ startTimer() {
2121
+ this.timer ??= setInterval(() => {
2122
+ this.now.set(Date.now());
2123
+ const live = Object.fromEntries(Object.entries(this.locks()).filter(([, until]) => until > this.now()));
2124
+ if (Object.keys(live).length === Object.keys(this.locks()).length) return;
2125
+ this.locks.set(live);
2126
+ writeLocks(this.storage(), live);
2127
+ if (!Object.keys(live).length) this.stopTimer();
2128
+ }, 1e3);
2129
+ }
2130
+ stopTimer() {
2131
+ clearInterval(this.timer);
2132
+ this.timer = void 0;
2133
+ }
2134
+ storage() {
2135
+ try {
2136
+ return this.doc.defaultView?.localStorage ?? null;
2137
+ } catch {
2138
+ return null;
2139
+ }
2140
+ }
2141
+ startExternal(key) {
2142
+ const p = this.external().find((x) => x.key === key);
2143
+ if (!p || this.busy()) return;
2144
+ this.busy.set(key);
2043
2145
  this.message.set(null);
2044
- this.http.get(`${this.config.apiBase}/user/sso/authorize-url`).subscribe({
2146
+ this.http.get(`${this.config.apiBase}/user/${key}/authorize-url`).subscribe({
2045
2147
  next: ({ url, state }) => {
2046
- this.auth.setTemp(KC_SSO_STATE_KEY, state);
2148
+ this.auth.setTemp(p.stateKey, state);
2047
2149
  this.auth.setTemp(KC_RETURN_URL_KEY, this.returnUrl);
2048
2150
  this.auth.redirect(url);
2049
2151
  },
2050
2152
  error: (err) => {
2051
2153
  this.busy.set("");
2052
2154
  this.message.set({
2053
- text: err.status === 404 ? `ระบบยังไม่ได้ตั้งค่าการเข้าสู่ระบบด้วย ${sso.label}` : kcErrorMessage(err, `ไม่สามารถเชื่อมต่อ ${sso.label} ได้ กรุณาลองใหม่อีกครั้ง`),
2155
+ text: err.status === 404 ? `ระบบยังไม่ได้ตั้งค่าการเข้าสู่ระบบด้วย ${p.label}` : kcErrorMessage(err, `ไม่สามารถเชื่อมต่อ ${p.label} ได้ กรุณาลองใหม่อีกครั้ง`),
2054
2156
  error: true
2055
2157
  });
2056
2158
  }
@@ -2080,11 +2182,15 @@ var KcLoginComponent = class KcLoginComponent {
2080
2182
  <h1 class="kc-auth-title">{{ config.appName }}</h1>
2081
2183
  <p class="kc-auth-subtitle">ลงชื่อเข้าสู่ระบบ</p>
2082
2184
 
2083
- @if (message(); as m) {
2185
+ @if (lockLeft()) {
2186
+ <div class="kc-auth-alert error" role="alert">
2187
+ เข้าสู่ระบบไม่สำเร็จเกินจำนวนครั้งที่กำหนด กรุณารอ {{ lockText() }} นาที แล้วลองใหม่อีกครั้ง
2188
+ </div>
2189
+ } @else if (message(); as m) {
2084
2190
  <div class="kc-auth-alert" [class.error]="m.error" [class.info]="!m.error" role="alert">{{ m.text }}</div>
2085
2191
  }
2086
2192
 
2087
- @if (config.passwordLogin) {
2193
+ @if (showPassword()) {
2088
2194
  <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
2089
2195
  <label class="kc-auth-field">
2090
2196
  <span>ชื่อผู้ใช้</span>
@@ -2094,31 +2200,34 @@ var KcLoginComponent = class KcLoginComponent {
2094
2200
  <span>รหัสผ่าน</span>
2095
2201
  <kc-password><input class="kc-auth-input" type="password" formControlName="password" autocomplete="current-password" /></kc-password>
2096
2202
  </label>
2097
- <!-- ปุ่มเข้าสู่ระบบกับ SSO อยู่บรรทัดเดียวกัน แบ่งครึ่ง — ปุ่ม SSO ใช้แค่ชื่อเพื่อให้พอดีครึ่งการ์ด -->
2203
+ <!-- ปุ่มเข้าสู่ระบบกับ SSO / ThaiD อยู่บรรทัดเดียวกัน แบ่งเท่ากัน — ปุ่มภายนอกใช้แค่ชื่อเพื่อให้พอดี -->
2098
2204
  <div class="kc-auth-btn-row">
2099
- <button type="submit" class="kc-auth-btn primary" [disabled]="!!busy()">
2100
- {{ busy() === 'password' ? 'กำลังเข้าสู่ระบบ...' : 'เข้าสู่ระบบ' }}
2205
+ <button type="submit" class="kc-auth-btn primary" [disabled]="!!busy() || !!lockLeft()">
2206
+ {{ busy() === 'password' ? 'กำลังเข้าสู่ระบบ...' : lockLeft() ? 'รอ ' + lockText() : 'เข้าสู่ระบบ' }}
2101
2207
  </button>
2102
- @if (config.sso; as sso) {
2103
- <button type="button" class="kc-auth-btn outline" [disabled]="!!busy()" (click)="startSso()"
2104
- [attr.title]="'เข้าสู่ระบบด้วย ' + sso.label">
2105
- {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : sso.label }}
2208
+ @for (p of external(); track p.key) {
2209
+ <button type="button" class="kc-auth-btn outline" [disabled]="!!busy()" (click)="startExternal(p.key)"
2210
+ [attr.title]="'เข้าสู่ระบบด้วย ' + p.label">
2211
+ {{ busy() === p.key ? 'กำลังเชื่อมต่อ...' : p.label }}
2106
2212
  </button>
2107
2213
  }
2108
2214
  </div>
2109
2215
  </form>
2110
- } @else if (config.sso; as sso) {
2111
- <!-- ปิดรหัสผ่าน: มีแต่ปุ่ม SSO ตรงกลาง -->
2216
+ } @else {
2217
+ <!-- ปิดรหัสผ่าน: มีแต่ปุ่ม SSO / ThaiD ตรงกลาง -->
2112
2218
  <div class="kc-auth-sso-only">
2113
- <button type="button" class="kc-auth-btn primary" [disabled]="!!busy()" (click)="startSso()">
2114
- {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : 'เข้าสู่ระบบด้วย ' + sso.label }}
2219
+ @for (p of external(); track p.key) {
2220
+ <button type="button" class="kc-auth-btn" [class.primary]="$first" [class.outline]="!$first" [disabled]="!!busy()"
2221
+ (click)="startExternal(p.key)">
2222
+ {{ busy() === p.key ? 'กำลังเชื่อมต่อ...' : 'เข้าสู่ระบบด้วย ' + p.label }}
2115
2223
  </button>
2224
+ }
2116
2225
  </div>
2117
2226
  }
2118
2227
 
2119
2228
  @if (config.register === 'form') {
2120
2229
  <p class="kc-auth-footer">ยังไม่มีบัญชี? <a class="kc-auth-link" [routerLink]="paths.register">ลงทะเบียน</a></p>
2121
- } @else if (config.register === 'sso' && config.sso) {
2230
+ } @else if (config.register === 'sso' && config.sso && methods().sso) {
2122
2231
  <p class="kc-auth-footer">ผู้ใช้ใหม่ กดปุ่ม {{ config.sso.label }} เพื่อลงทะเบียน</p>
2123
2232
  }
2124
2233
  <ng-content />
@@ -2221,11 +2330,15 @@ i0.ɵɵngDeclareClassMetadata({
2221
2330
  <h1 class="kc-auth-title">{{ config.appName }}</h1>
2222
2331
  <p class="kc-auth-subtitle">ลงชื่อเข้าสู่ระบบ</p>
2223
2332
 
2224
- @if (message(); as m) {
2333
+ @if (lockLeft()) {
2334
+ <div class="kc-auth-alert error" role="alert">
2335
+ เข้าสู่ระบบไม่สำเร็จเกินจำนวนครั้งที่กำหนด กรุณารอ {{ lockText() }} นาที แล้วลองใหม่อีกครั้ง
2336
+ </div>
2337
+ } @else if (message(); as m) {
2225
2338
  <div class="kc-auth-alert" [class.error]="m.error" [class.info]="!m.error" role="alert">{{ m.text }}</div>
2226
2339
  }
2227
2340
 
2228
- @if (config.passwordLogin) {
2341
+ @if (showPassword()) {
2229
2342
  <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
2230
2343
  <label class="kc-auth-field">
2231
2344
  <span>ชื่อผู้ใช้</span>
@@ -2235,31 +2348,34 @@ i0.ɵɵngDeclareClassMetadata({
2235
2348
  <span>รหัสผ่าน</span>
2236
2349
  <kc-password><input class="kc-auth-input" type="password" formControlName="password" autocomplete="current-password" /></kc-password>
2237
2350
  </label>
2238
- <!-- ปุ่มเข้าสู่ระบบกับ SSO อยู่บรรทัดเดียวกัน แบ่งครึ่ง — ปุ่ม SSO ใช้แค่ชื่อเพื่อให้พอดีครึ่งการ์ด -->
2351
+ <!-- ปุ่มเข้าสู่ระบบกับ SSO / ThaiD อยู่บรรทัดเดียวกัน แบ่งเท่ากัน — ปุ่มภายนอกใช้แค่ชื่อเพื่อให้พอดี -->
2239
2352
  <div class="kc-auth-btn-row">
2240
- <button type="submit" class="kc-auth-btn primary" [disabled]="!!busy()">
2241
- {{ busy() === 'password' ? 'กำลังเข้าสู่ระบบ...' : 'เข้าสู่ระบบ' }}
2353
+ <button type="submit" class="kc-auth-btn primary" [disabled]="!!busy() || !!lockLeft()">
2354
+ {{ busy() === 'password' ? 'กำลังเข้าสู่ระบบ...' : lockLeft() ? 'รอ ' + lockText() : 'เข้าสู่ระบบ' }}
2242
2355
  </button>
2243
- @if (config.sso; as sso) {
2244
- <button type="button" class="kc-auth-btn outline" [disabled]="!!busy()" (click)="startSso()"
2245
- [attr.title]="'เข้าสู่ระบบด้วย ' + sso.label">
2246
- {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : sso.label }}
2356
+ @for (p of external(); track p.key) {
2357
+ <button type="button" class="kc-auth-btn outline" [disabled]="!!busy()" (click)="startExternal(p.key)"
2358
+ [attr.title]="'เข้าสู่ระบบด้วย ' + p.label">
2359
+ {{ busy() === p.key ? 'กำลังเชื่อมต่อ...' : p.label }}
2247
2360
  </button>
2248
2361
  }
2249
2362
  </div>
2250
2363
  </form>
2251
- } @else if (config.sso; as sso) {
2252
- <!-- ปิดรหัสผ่าน: มีแต่ปุ่ม SSO ตรงกลาง -->
2364
+ } @else {
2365
+ <!-- ปิดรหัสผ่าน: มีแต่ปุ่ม SSO / ThaiD ตรงกลาง -->
2253
2366
  <div class="kc-auth-sso-only">
2254
- <button type="button" class="kc-auth-btn primary" [disabled]="!!busy()" (click)="startSso()">
2255
- {{ busy() === 'sso' ? 'กำลังเชื่อมต่อ...' : 'เข้าสู่ระบบด้วย ' + sso.label }}
2367
+ @for (p of external(); track p.key) {
2368
+ <button type="button" class="kc-auth-btn" [class.primary]="$first" [class.outline]="!$first" [disabled]="!!busy()"
2369
+ (click)="startExternal(p.key)">
2370
+ {{ busy() === p.key ? 'กำลังเชื่อมต่อ...' : 'เข้าสู่ระบบด้วย ' + p.label }}
2256
2371
  </button>
2372
+ }
2257
2373
  </div>
2258
2374
  }
2259
2375
 
2260
2376
  @if (config.register === 'form') {
2261
2377
  <p class="kc-auth-footer">ยังไม่มีบัญชี? <a class="kc-auth-link" [routerLink]="paths.register">ลงทะเบียน</a></p>
2262
- } @else if (config.register === 'sso' && config.sso) {
2378
+ } @else if (config.register === 'sso' && config.sso && methods().sso) {
2263
2379
  <p class="kc-auth-footer">ผู้ใช้ใหม่ กดปุ่ม {{ config.sso.label }} เพื่อลงทะเบียน</p>
2264
2380
  }
2265
2381
  <ng-content />
@@ -2678,22 +2794,35 @@ i0.ɵɵngDeclareClassMetadata({
2678
2794
  }],
2679
2795
  ctorParameters: () => []
2680
2796
  });
2681
- var KcSsoCallbackComponent = class KcSsoCallbackComponent {
2797
+ const CALLBACK_TEMPLATE = `
2798
+ <div class="kc-auth-screen">
2799
+ <div class="kc-auth-card center">
2800
+ <h1 class="kc-auth-title">{{ label }}</h1>
2801
+ @if (failed()) {
2802
+ <div class="kc-auth-alert error" role="alert" style="margin-top: 16px">{{ message() }}</div>
2803
+ <a class="kc-auth-btn" [routerLink]="paths.login">กลับไปหน้าเข้าสู่ระบบ</a>
2804
+ } @else {
2805
+ <p class="kc-auth-subtitle" style="margin-bottom: 0">{{ message() }}</p>
2806
+ }
2807
+ </div>
2808
+ </div>
2809
+ `;
2810
+ var KcExternalCallbackBase = class KcExternalCallbackBase {
2682
2811
  config = inject(KC_AUTH_CONFIG);
2683
2812
  auth = inject(KcAuthService);
2684
2813
  http = inject(HttpClient);
2685
- route = inject(ActivatedRoute);
2686
2814
  router = inject(Router);
2815
+ route = inject(ActivatedRoute);
2687
2816
  paths = KC_AUTH_PATHS;
2688
- label = this.config.sso?.label ?? "SSO";
2689
- message = signal(`กำลังเข้าสู่ระบบผ่าน ${this.label}...`, ...ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []);
2817
+ message = signal("", ...ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []);
2690
2818
  failed = signal(false, ...ngDevMode ? [{ debugName: "failed" }] : /* istanbul ignore next */ []);
2691
2819
  constructor() {
2692
2820
  ensureAuthStyles();
2693
2821
  }
2694
2822
  ngOnInit() {
2823
+ this.message.set(`กำลังเข้าสู่ระบบผ่าน ${this.label}...`);
2695
2824
  const params = this.route.snapshot.queryParamMap;
2696
- const expectedState = this.auth.takeTemp(KC_SSO_STATE_KEY);
2825
+ const expectedState = this.auth.takeTemp(this.stateKey);
2697
2826
  const code = params.get("code");
2698
2827
  if (params.get("error")) {
2699
2828
  this.fail(`${this.label} ปฏิเสธการเข้าสู่ระบบ กรุณาลองใหม่อีกครั้ง`);
@@ -2701,9 +2830,9 @@ var KcSsoCallbackComponent = class KcSsoCallbackComponent {
2701
2830
  }
2702
2831
  if (!code) {
2703
2832
  this.message.set(`กำลังเชื่อมต่อกับ ${this.label}...`);
2704
- this.http.get(`${this.config.apiBase}/user/sso/authorize-url`).subscribe({
2833
+ this.http.get(`${this.config.apiBase}/user/${this.endpoint}/authorize-url`).subscribe({
2705
2834
  next: ({ url, state }) => {
2706
- this.auth.setTemp(KC_SSO_STATE_KEY, state);
2835
+ this.auth.setTemp(this.stateKey, state);
2707
2836
  this.auth.redirect(url);
2708
2837
  },
2709
2838
  error: () => this.fail(`ไม่สามารถเชื่อมต่อ ${this.label} ได้ กรุณาเริ่มเข้าสู่ระบบใหม่`)
@@ -2714,14 +2843,52 @@ var KcSsoCallbackComponent = class KcSsoCallbackComponent {
2714
2843
  this.fail("คำขอเข้าสู่ระบบไม่ถูกต้อง (state ไม่ตรงกัน) กรุณาเริ่มเข้าสู่ระบบใหม่");
2715
2844
  return;
2716
2845
  }
2846
+ this.exchange(code);
2847
+ }
2848
+ signIn(session) {
2849
+ this.auth.start(session);
2850
+ this.router.navigateByUrl(this.auth.safeReturnUrl(this.auth.takeTemp(KC_RETURN_URL_KEY)), { replaceUrl: true });
2851
+ }
2852
+ fail(message) {
2853
+ this.message.set(message);
2854
+ this.failed.set(true);
2855
+ }
2856
+ static ɵfac = i0.ɵɵngDeclareFactory({
2857
+ minVersion: "12.0.0",
2858
+ version: "22.2.1",
2859
+ ngImport: i0,
2860
+ type: KcExternalCallbackBase,
2861
+ deps: [],
2862
+ target: i0.ɵɵFactoryTarget.Directive
2863
+ });
2864
+ static ɵdir = i0.ɵɵngDeclareDirective({
2865
+ minVersion: "14.0.0",
2866
+ version: "22.2.1",
2867
+ type: KcExternalCallbackBase,
2868
+ isStandalone: true,
2869
+ ngImport: i0
2870
+ });
2871
+ };
2872
+ i0.ɵɵngDeclareClassMetadata({
2873
+ minVersion: "12.0.0",
2874
+ version: "22.2.1",
2875
+ ngImport: i0,
2876
+ type: KcExternalCallbackBase,
2877
+ decorators: [{ type: Directive }],
2878
+ ctorParameters: () => []
2879
+ });
2880
+ var KcSsoCallbackComponent = class KcSsoCallbackComponent extends KcExternalCallbackBase {
2881
+ label = this.config.sso?.label ?? "SSO";
2882
+ endpoint = "sso";
2883
+ stateKey = KC_SSO_STATE_KEY;
2884
+ exchange(code) {
2717
2885
  this.http.post(`${this.config.apiBase}/user/sso/callback`, { code }).subscribe({
2718
2886
  next: (res) => {
2719
2887
  if (res.mode === "signed-in" && res.token && res.user) {
2720
- this.auth.start({
2888
+ this.signIn({
2721
2889
  token: res.token,
2722
2890
  user: res.user
2723
2891
  });
2724
- this.router.navigateByUrl(this.auth.safeReturnUrl(this.auth.takeTemp(KC_RETURN_URL_KEY)), { replaceUrl: true });
2725
2892
  return;
2726
2893
  }
2727
2894
  if (res.mode === "register" && res.ticket) {
@@ -2735,16 +2902,12 @@ var KcSsoCallbackComponent = class KcSsoCallbackComponent {
2735
2902
  error: (err) => this.fail(kcErrorMessage(err, `ไม่สามารถเข้าสู่ระบบผ่าน ${this.label} ได้`))
2736
2903
  });
2737
2904
  }
2738
- fail(message) {
2739
- this.message.set(message);
2740
- this.failed.set(true);
2741
- }
2742
2905
  static ɵfac = i0.ɵɵngDeclareFactory({
2743
2906
  minVersion: "12.0.0",
2744
2907
  version: "22.2.1",
2745
2908
  ngImport: i0,
2746
2909
  type: KcSsoCallbackComponent,
2747
- deps: [],
2910
+ deps: null,
2748
2911
  target: i0.ɵɵFactoryTarget.Component
2749
2912
  });
2750
2913
  static ɵcmp = i0.ɵɵngDeclareComponent({
@@ -2753,20 +2916,9 @@ var KcSsoCallbackComponent = class KcSsoCallbackComponent {
2753
2916
  type: KcSsoCallbackComponent,
2754
2917
  isStandalone: true,
2755
2918
  selector: "kc-sso-callback",
2919
+ usesInheritance: true,
2756
2920
  ngImport: i0,
2757
- template: `
2758
- <div class="kc-auth-screen">
2759
- <div class="kc-auth-card center">
2760
- <h1 class="kc-auth-title">{{ label }}</h1>
2761
- @if (failed()) {
2762
- <div class="kc-auth-alert error" role="alert" style="margin-top: 16px">{{ message() }}</div>
2763
- <a class="kc-auth-btn" [routerLink]="paths.login">กลับไปหน้าเข้าสู่ระบบ</a>
2764
- } @else {
2765
- <p class="kc-auth-subtitle" style="margin-bottom: 0">{{ message() }}</p>
2766
- }
2767
- </div>
2768
- </div>
2769
- `,
2921
+ template: "\n <div class=\"kc-auth-screen\">\n <div class=\"kc-auth-card center\">\n <h1 class=\"kc-auth-title\">{{ label }}</h1>\n @if (failed()) {\n <div class=\"kc-auth-alert error\" role=\"alert\" style=\"margin-top: 16px\">{{ message() }}</div>\n <a class=\"kc-auth-btn\" [routerLink]=\"paths.login\">กลับไปหน้าเข้าสู่ระบบ</a>\n } @else {\n <p class=\"kc-auth-subtitle\" style=\"margin-bottom: 0\">{{ message() }}</p>\n }\n </div>\n </div>\n",
2770
2922
  isInline: true,
2771
2923
  dependencies: [{
2772
2924
  kind: "directive",
@@ -2800,22 +2952,73 @@ i0.ɵɵngDeclareClassMetadata({
2800
2952
  selector: "kc-sso-callback",
2801
2953
  standalone: true,
2802
2954
  imports: [RouterLink],
2803
- template: `
2804
- <div class="kc-auth-screen">
2805
- <div class="kc-auth-card center">
2806
- <h1 class="kc-auth-title">{{ label }}</h1>
2807
- @if (failed()) {
2808
- <div class="kc-auth-alert error" role="alert" style="margin-top: 16px">{{ message() }}</div>
2809
- <a class="kc-auth-btn" [routerLink]="paths.login">กลับไปหน้าเข้าสู่ระบบ</a>
2810
- } @else {
2811
- <p class="kc-auth-subtitle" style="margin-bottom: 0">{{ message() }}</p>
2812
- }
2813
- </div>
2814
- </div>
2815
- `
2955
+ template: CALLBACK_TEMPLATE
2816
2956
  }]
2817
- }],
2818
- ctorParameters: () => []
2957
+ }]
2958
+ });
2959
+ var KcThaidCallbackComponent = class KcThaidCallbackComponent extends KcExternalCallbackBase {
2960
+ label = this.config.thaid?.label ?? "ThaiD";
2961
+ endpoint = "thaid";
2962
+ stateKey = KC_THAID_STATE_KEY;
2963
+ exchange(code) {
2964
+ this.http.post(`${this.config.apiBase}/user/thaid/callback`, { code }).subscribe({
2965
+ next: (res) => res?.token && res.user ? this.signIn(res) : this.fail("ไม่สามารถเข้าสู่ระบบได้ กรุณาลองใหม่อีกครั้ง"),
2966
+ error: (err) => this.fail(kcErrorMessage(err, `ไม่สามารถเข้าสู่ระบบผ่าน ${this.label} ได้`))
2967
+ });
2968
+ }
2969
+ static ɵfac = i0.ɵɵngDeclareFactory({
2970
+ minVersion: "12.0.0",
2971
+ version: "22.2.1",
2972
+ ngImport: i0,
2973
+ type: KcThaidCallbackComponent,
2974
+ deps: null,
2975
+ target: i0.ɵɵFactoryTarget.Component
2976
+ });
2977
+ static ɵcmp = i0.ɵɵngDeclareComponent({
2978
+ minVersion: "17.0.0",
2979
+ version: "22.2.1",
2980
+ type: KcThaidCallbackComponent,
2981
+ isStandalone: true,
2982
+ selector: "kc-thaid-callback",
2983
+ usesInheritance: true,
2984
+ ngImport: i0,
2985
+ template: "\n <div class=\"kc-auth-screen\">\n <div class=\"kc-auth-card center\">\n <h1 class=\"kc-auth-title\">{{ label }}</h1>\n @if (failed()) {\n <div class=\"kc-auth-alert error\" role=\"alert\" style=\"margin-top: 16px\">{{ message() }}</div>\n <a class=\"kc-auth-btn\" [routerLink]=\"paths.login\">กลับไปหน้าเข้าสู่ระบบ</a>\n } @else {\n <p class=\"kc-auth-subtitle\" style=\"margin-bottom: 0\">{{ message() }}</p>\n }\n </div>\n </div>\n",
2986
+ isInline: true,
2987
+ dependencies: [{
2988
+ kind: "directive",
2989
+ type: RouterLink,
2990
+ selector: "[routerLink]",
2991
+ inputs: [
2992
+ "target",
2993
+ "queryParams",
2994
+ "fragment",
2995
+ "queryParamsHandling",
2996
+ "state",
2997
+ "info",
2998
+ "relativeTo",
2999
+ "preserveFragment",
3000
+ "skipLocationChange",
3001
+ "replaceUrl",
3002
+ "browserUrl",
3003
+ "routerLink"
3004
+ ]
3005
+ }]
3006
+ });
3007
+ };
3008
+ i0.ɵɵngDeclareClassMetadata({
3009
+ minVersion: "12.0.0",
3010
+ version: "22.2.1",
3011
+ ngImport: i0,
3012
+ type: KcThaidCallbackComponent,
3013
+ decorators: [{
3014
+ type: Component,
3015
+ args: [{
3016
+ selector: "kc-thaid-callback",
3017
+ standalone: true,
3018
+ imports: [RouterLink],
3019
+ template: CALLBACK_TEMPLATE
3020
+ }]
3021
+ }]
2819
3022
  });
2820
3023
  function kcAuthRoutes() {
2821
3024
  return [
@@ -2833,6 +3036,11 @@ function kcAuthRoutes() {
2833
3036
  path: "sso-callback",
2834
3037
  component: KcSsoCallbackComponent,
2835
3038
  title: "เข้าสู่ระบบ"
3039
+ },
3040
+ {
3041
+ path: "thaid-callback",
3042
+ component: KcThaidCallbackComponent,
3043
+ title: "เข้าสู่ระบบ"
2836
3044
  }
2837
3045
  ];
2838
3046
  }
@@ -3163,6 +3371,33 @@ var KcConfigUsersComponent = class KcConfigUsersComponent {
3163
3371
  search = signal("", ...ngDevMode ? [{ debugName: "search" }] : /* istanbul ignore next */ []);
3164
3372
  editing = signal(null, ...ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []);
3165
3373
  myId = computed(() => this.auth.user()?.id ?? "", ...ngDevMode ? [{ debugName: "myId" }] : /* istanbul ignore next */ []);
3374
+ methodsSaved = signal(null, ...ngDevMode ? [{ debugName: "methodsSaved" }] : /* istanbul ignore next */ []);
3375
+ methodsDraft = signal(null, ...ngDevMode ? [{ debugName: "methodsDraft" }] : /* istanbul ignore next */ []);
3376
+ savingMethods = signal(false, ...ngDevMode ? [{ debugName: "savingMethods" }] : /* istanbul ignore next */ []);
3377
+ methodsError = signal("", ...ngDevMode ? [{ debugName: "methodsError" }] : /* istanbul ignore next */ []);
3378
+ methodOptions = computed(() => [
3379
+ ...this.config.passwordLogin ? [{
3380
+ key: "password",
3381
+ label: "ชื่อผู้ใช้ / รหัสผ่าน"
3382
+ }] : [],
3383
+ ...this.config.sso ? [{
3384
+ key: "sso",
3385
+ label: this.config.sso.label
3386
+ }] : [],
3387
+ ...this.config.thaid ? [{
3388
+ key: "thaid",
3389
+ label: this.config.thaid.label
3390
+ }] : []
3391
+ ], ...ngDevMode ? [{ debugName: "methodOptions" }] : /* istanbul ignore next */ []);
3392
+ enabledCount = computed(() => {
3393
+ const m = this.methodsDraft();
3394
+ return m ? this.methodOptions().filter((o) => m[o.key]).length : 0;
3395
+ }, ...ngDevMode ? [{ debugName: "enabledCount" }] : /* istanbul ignore next */ []);
3396
+ methodsDirty = computed(() => {
3397
+ const a = this.methodsDraft();
3398
+ const b = this.methodsSaved();
3399
+ return !!a && !!b && (a.password !== b.password || a.sso !== b.sso || a.thaid !== b.thaid);
3400
+ }, ...ngDevMode ? [{ debugName: "methodsDirty" }] : /* istanbul ignore next */ []);
3166
3401
  orgOptions = computed(() => [{
3167
3402
  value: "",
3168
3403
  label: "-- ไม่ระบุ --"
@@ -3188,6 +3423,51 @@ var KcConfigUsersComponent = class KcConfigUsersComponent {
3188
3423
  }
3189
3424
  ngOnInit() {
3190
3425
  this.load();
3426
+ this.http.get(`${this.config.apiBase}/user/login-methods`).subscribe({
3427
+ next: (m) => {
3428
+ const v = {
3429
+ password: m?.password !== false,
3430
+ sso: m?.sso !== false,
3431
+ thaid: m?.thaid !== false
3432
+ };
3433
+ this.methodsSaved.set(v);
3434
+ this.methodsDraft.set({ ...v });
3435
+ },
3436
+ error: () => {}
3437
+ });
3438
+ }
3439
+ toggleMethod(key, on) {
3440
+ const m = this.methodsDraft();
3441
+ if (!m) return;
3442
+ const next = {
3443
+ ...m,
3444
+ [key]: on
3445
+ };
3446
+ if (!this.methodOptions().some((o) => next[o.key])) return;
3447
+ this.methodsError.set("");
3448
+ this.methodsDraft.set(next);
3449
+ }
3450
+ saveMethods() {
3451
+ const m = this.methodsDraft();
3452
+ if (!m || this.savingMethods()) return;
3453
+ if (!this.methodOptions().some((o) => m[o.key])) {
3454
+ this.methodsError.set("ต้องเปิดการเข้าสู่ระบบอย่างน้อย 1 วิธี");
3455
+ return;
3456
+ }
3457
+ this.savingMethods.set(true);
3458
+ this.http.put(`${this.config.apiBase}/config/login-methods`, m).subscribe({
3459
+ next: (res) => {
3460
+ this.savingMethods.set(false);
3461
+ this.methodsSaved.set({ ...m });
3462
+ this.methodsError.set("");
3463
+ this.error.set("");
3464
+ this.success.set(res?.message || "บันทึกวิธีเข้าสู่ระบบเรียบร้อยแล้ว");
3465
+ },
3466
+ error: (err) => {
3467
+ this.savingMethods.set(false);
3468
+ this.methodsError.set(kcErrorMessage(err, "บันทึกวิธีเข้าสู่ระบบไม่สำเร็จ"));
3469
+ }
3470
+ });
3191
3471
  }
3192
3472
  load() {
3193
3473
  this.loading.set(true);
@@ -3286,6 +3566,30 @@ var KcConfigUsersComponent = class KcConfigUsersComponent {
3286
3566
  <div class="kc-auth-alert success" role="status">{{ success() }}</div>
3287
3567
  }
3288
3568
 
3569
+ @if (methodOptions().length > 1 && methodsDraft(); as m) {
3570
+ <div class="kc-auth-panel">
3571
+ <h2 class="kc-auth-panel-title">วิธีเข้าสู่ระบบ</h2>
3572
+ <div class="kc-auth-methods">
3573
+ @for (o of methodOptions(); track o.key) {
3574
+ <label class="kc-auth-check">
3575
+ <input type="checkbox" [checked]="m[o.key]" [disabled]="savingMethods() || (m[o.key] && enabledCount() === 1)"
3576
+ (change)="toggleMethod(o.key, $any($event.target).checked)" />
3577
+ {{ o.label }}
3578
+ </label>
3579
+ }
3580
+ <button type="button" class="kc-auth-btn primary" [disabled]="savingMethods() || !methodsDirty()" (click)="saveMethods()">
3581
+ {{ savingMethods() ? 'กำลังบันทึก...' : 'บันทึก' }}
3582
+ </button>
3583
+ </div>
3584
+ @if (methodsError()) {
3585
+ <div class="kc-auth-alert error" role="alert" style="margin: 10px 0 0">{{ methodsError() }}</div>
3586
+ }
3587
+ <p class="kc-auth-hint" style="margin: 10px 0 0">
3588
+ ต้องเปิดอย่างน้อย 1 วิธี — ก่อนปิดวิธีอื่น ให้ลองเข้าสู่ระบบด้วยวิธีที่จะเหลือให้ได้ก่อน มีผลกับการเข้าสู่ระบบครั้งถัดไป (ผู้ที่เข้าอยู่แล้วใช้งานต่อได้)
3589
+ </p>
3590
+ </div>
3591
+ }
3592
+
3289
3593
  <div class="kc-auth-panel">
3290
3594
  <input class="kc-auth-input" type="search" [placeholder]="'ค้นหาชื่อผู้ใช้ ชื่อ-สกุล ' + config.orgLabel + ' ระดับผู้ใช้ หรืออีเมล'"
3291
3595
  [ngModel]="search()" (ngModelChange)="search.set($event)" aria-label="ค้นหาผู้ใช้" />
@@ -3499,6 +3803,30 @@ i0.ɵɵngDeclareClassMetadata({
3499
3803
  <div class="kc-auth-alert success" role="status">{{ success() }}</div>
3500
3804
  }
3501
3805
 
3806
+ @if (methodOptions().length > 1 && methodsDraft(); as m) {
3807
+ <div class="kc-auth-panel">
3808
+ <h2 class="kc-auth-panel-title">วิธีเข้าสู่ระบบ</h2>
3809
+ <div class="kc-auth-methods">
3810
+ @for (o of methodOptions(); track o.key) {
3811
+ <label class="kc-auth-check">
3812
+ <input type="checkbox" [checked]="m[o.key]" [disabled]="savingMethods() || (m[o.key] && enabledCount() === 1)"
3813
+ (change)="toggleMethod(o.key, $any($event.target).checked)" />
3814
+ {{ o.label }}
3815
+ </label>
3816
+ }
3817
+ <button type="button" class="kc-auth-btn primary" [disabled]="savingMethods() || !methodsDirty()" (click)="saveMethods()">
3818
+ {{ savingMethods() ? 'กำลังบันทึก...' : 'บันทึก' }}
3819
+ </button>
3820
+ </div>
3821
+ @if (methodsError()) {
3822
+ <div class="kc-auth-alert error" role="alert" style="margin: 10px 0 0">{{ methodsError() }}</div>
3823
+ }
3824
+ <p class="kc-auth-hint" style="margin: 10px 0 0">
3825
+ ต้องเปิดอย่างน้อย 1 วิธี — ก่อนปิดวิธีอื่น ให้ลองเข้าสู่ระบบด้วยวิธีที่จะเหลือให้ได้ก่อน มีผลกับการเข้าสู่ระบบครั้งถัดไป (ผู้ที่เข้าอยู่แล้วใช้งานต่อได้)
3826
+ </p>
3827
+ </div>
3828
+ }
3829
+
3502
3830
  <div class="kc-auth-panel">
3503
3831
  <input class="kc-auth-input" type="search" [placeholder]="'ค้นหาชื่อผู้ใช้ ชื่อ-สกุล ' + config.orgLabel + ' ระดับผู้ใช้ หรืออีเมล'"
3504
3832
  [ngModel]="search()" (ngModelChange)="search.set($event)" aria-label="ค้นหาผู้ใช้" />
@@ -4115,6 +4443,6 @@ i0.ɵɵngDeclareClassMetadata({
4115
4443
  }],
4116
4444
  ctorParameters: () => []
4117
4445
  });
4118
- export { AnchoredPopup, ClockPickerDirective, ClockPickerPopupComponent, DatePickerDirective, DatePickerPopupComponent, KC_AUTH_CONFIG, KC_AUTH_PATHS, KC_RETURN_URL_KEY, KC_SSO_STATE_KEY, KC_SSO_TICKET_KEY, KcAuthService, KcConfigKclluComponent, KcConfigUsersComponent, KcLoginComponent, KcPasswordToggleComponent, KcProfileComponent, KcRegisterComponent, KcSsoCallbackComponent, PICKER_CSS, SearchSelectComponent, ThaiDatePickerComponent, ensurePickerStyles, fixThaiText, formatDate, kcAdminGuard, kcAuthGuard, kcAuthInterceptor, kcAuthRoutes, kcErrorMessage, kcTokenExpired, parseDate, provideKcAuth, provideThaiPasteFix, resolveKcAuthConfig, toBuddhistText, toChristianText };
4446
+ export { AnchoredPopup, ClockPickerDirective, ClockPickerPopupComponent, DatePickerDirective, DatePickerPopupComponent, KC_AUTH_CONFIG, KC_AUTH_PATHS, KC_LOGIN_LOCK_KEY, KC_LOGIN_LOCK_SECONDS, KC_RETURN_URL_KEY, KC_SSO_STATE_KEY, KC_SSO_TICKET_KEY, KC_THAID_STATE_KEY, KcAuthService, KcConfigKclluComponent, KcConfigUsersComponent, KcLoginComponent, KcPasswordToggleComponent, KcProfileComponent, KcRegisterComponent, KcSsoCallbackComponent, KcThaidCallbackComponent, PICKER_CSS, SearchSelectComponent, ThaiDatePickerComponent, ensurePickerStyles, fixThaiText, formatDate, kcAdminGuard, kcAuthGuard, kcAuthInterceptor, kcAuthRoutes, kcErrorMessage, kcLoginRetryAfter, kcTokenExpired, parseDate, provideKcAuth, provideThaiPasteFix, resolveKcAuthConfig, toBuddhistText, toChristianText };
4119
4447
 
4120
4448
  //# sourceMappingURL=kengcom202-kcore-ui.mjs.map