@dbx-tools/auth 0.9.31 → 0.9.36

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.
Files changed (73) hide show
  1. package/README.md +49 -97
  2. package/index.ts +8 -26
  3. package/lib/index.d.ts +8 -26
  4. package/lib/index.js +6 -22
  5. package/lib/src/{databricks-cli.d.ts → _databricks-cli.d.ts} +3 -7
  6. package/lib/src/_databricks-cli.js +172 -0
  7. package/lib/src/_errors.d.ts +5 -0
  8. package/lib/src/_errors.js +10 -0
  9. package/lib/src/{lifecycle.d.ts → _lifecycle.d.ts} +5 -5
  10. package/lib/src/_lifecycle.js +214 -0
  11. package/lib/src/_logging.d.ts +1 -1
  12. package/lib/src/_logging.js +2 -2
  13. package/lib/src/{personal-access-token.d.ts → _personal-access-token.d.ts} +1 -1
  14. package/lib/src/_personal-access-token.js +30 -0
  15. package/lib/src/_profile-config.d.ts +43 -0
  16. package/lib/src/_profile-config.js +443 -0
  17. package/lib/src/{service-principal.d.ts → _service-principal.d.ts} +1 -1
  18. package/lib/src/_service-principal.js +69 -0
  19. package/lib/src/{storage.d.ts → _storage.d.ts} +4 -6
  20. package/lib/src/_storage.js +94 -0
  21. package/lib/src/_types.d.ts +40 -0
  22. package/lib/src/_types.js +2 -0
  23. package/lib/src/client.d.ts +30 -0
  24. package/lib/src/client.js +223 -0
  25. package/lib/src/config.d.ts +36 -0
  26. package/lib/src/config.js +15 -0
  27. package/lib/src/profile.d.ts +11 -0
  28. package/lib/src/profile.js +21 -0
  29. package/lib/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +34 -10
  31. package/src/{databricks-cli.ts → _databricks-cli.ts} +20 -109
  32. package/src/_errors.ts +11 -0
  33. package/src/{lifecycle.ts → _lifecycle.ts} +24 -48
  34. package/src/_logging.ts +2 -2
  35. package/src/{personal-access-token.ts → _personal-access-token.ts} +1 -1
  36. package/src/_profile-config.ts +545 -0
  37. package/src/{service-principal.ts → _service-principal.ts} +2 -2
  38. package/src/_storage.ts +109 -0
  39. package/src/_types.ts +45 -0
  40. package/src/client.ts +291 -0
  41. package/src/config.ts +44 -0
  42. package/src/profile.ts +33 -0
  43. package/lib/exports.d.ts +0 -2
  44. package/lib/exports.js +0 -3
  45. package/lib/src/_profile-core.d.ts +0 -28
  46. package/lib/src/_profile-core.js +0 -216
  47. package/lib/src/_profile.d.ts +0 -15
  48. package/lib/src/_profile.js +0 -236
  49. package/lib/src/databricks-auth.d.ts +0 -11
  50. package/lib/src/databricks-auth.js +0 -314
  51. package/lib/src/databricks-cli.js +0 -239
  52. package/lib/src/errors.d.ts +0 -7
  53. package/lib/src/errors.js +0 -14
  54. package/lib/src/generated/databricks-cli-assets.json +0 -33
  55. package/lib/src/http-client.d.ts +0 -23
  56. package/lib/src/http-client.js +0 -116
  57. package/lib/src/lifecycle.js +0 -229
  58. package/lib/src/node-storage.d.ts +0 -20
  59. package/lib/src/node-storage.js +0 -207
  60. package/lib/src/personal-access-token.js +0 -30
  61. package/lib/src/service-principal.js +0 -69
  62. package/lib/src/storage.js +0 -98
  63. package/lib/src/types.d.ts +0 -157
  64. package/lib/src/types.js +0 -67
  65. package/src/_profile-core.ts +0 -258
  66. package/src/_profile.ts +0 -319
  67. package/src/databricks-auth.ts +0 -416
  68. package/src/errors.ts +0 -16
  69. package/src/generated/databricks-cli-assets.json +0 -33
  70. package/src/http-client.ts +0 -147
  71. package/src/node-storage.ts +0 -230
  72. package/src/storage.ts +0 -104
  73. package/src/types.ts +0 -188
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbx-tools/auth",
3
- "description": "Persistent Databricks authentication with CLI-owned user credentials, App auth, profile resolution, and portable storage adapters",
3
+ "description": "Databricks profile resolution and token or authentication-header production for Node.js and Bun",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/reggie-db/dbx-tools.git",
@@ -19,14 +19,14 @@
19
19
  "projen": "projen"
20
20
  },
21
21
  "devDependencies": {
22
- "@types/bun": "1.3.14",
23
22
  "@types/ini": "^4.1.1",
24
23
  "@types/node": "^24.6.0",
25
24
  "typescript": "^5.9.3"
26
25
  },
27
26
  "dependencies": {
28
- "@dbx-tools/core": "^0.9.31",
29
- "@dbx-tools/shared-core": "^0.9.31",
27
+ "@dbx-tools/core": "^0.9.36",
28
+ "@dbx-tools/shared-auth": "^0.9.36",
29
+ "@dbx-tools/shared-core": "^0.9.36",
30
30
  "ini": "^6.0.0",
31
31
  "oauth4webapi": "^3.8.8"
32
32
  },
@@ -41,17 +41,46 @@
41
41
  "types": "./lib/index.d.ts",
42
42
  "default": "./lib/index.js"
43
43
  },
44
+ "./client": {
45
+ "types": "./lib/src/client.d.ts",
46
+ "default": "./lib/src/client.js"
47
+ },
48
+ "./config": {
49
+ "types": "./lib/src/config.d.ts",
50
+ "default": "./lib/src/config.js"
51
+ },
52
+ "./profile": {
53
+ "types": "./lib/src/profile.d.ts",
54
+ "default": "./lib/src/profile.js"
55
+ },
44
56
  "./package.json": "./package.json"
45
57
  }
46
58
  },
47
- "version": "0.9.31",
59
+ "version": "0.9.36",
48
60
  "types": "./lib/index.d.ts",
61
+ "dbxToolsConfig": {
62
+ "tags": [
63
+ "node"
64
+ ]
65
+ },
49
66
  "type": "module",
50
67
  "exports": {
51
68
  ".": {
52
69
  "types": "./lib/index.d.ts",
53
70
  "default": "./lib/index.js"
54
71
  },
72
+ "./client": {
73
+ "types": "./lib/src/client.d.ts",
74
+ "default": "./lib/src/client.js"
75
+ },
76
+ "./config": {
77
+ "types": "./lib/src/config.d.ts",
78
+ "default": "./lib/src/config.js"
79
+ },
80
+ "./profile": {
81
+ "types": "./lib/src/profile.d.ts",
82
+ "default": "./lib/src/profile.js"
83
+ },
55
84
  "./package.json": "./package.json"
56
85
  },
57
86
  "files": [
@@ -59,10 +88,5 @@
59
88
  "src",
60
89
  "lib"
61
90
  ],
62
- "dbxToolsConfig": {
63
- "tags": [
64
- "node"
65
- ]
66
- },
67
91
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
68
92
  }
@@ -1,60 +1,34 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
-
4
- import * as bin from "@dbx-tools/core/bin";
5
1
  import * as exec from "@dbx-tools/core/exec";
6
2
  import * as stringUtils from "@dbx-tools/shared-core/string-utils";
7
3
 
8
- import cliAssets from "./generated/databricks-cli-assets.json" with { type: "json" };
4
+ import { AuthError } from "./_errors.ts";
9
5
  import { authLogger, failureMetadata, tokenMetadata } from "./_logging.ts";
10
- import { AuthError } from "./errors.ts";
11
- import type { Token, TokenProvider } from "./types.ts";
6
+ import type { Token, TokenProvider } from "./_types.ts";
12
7
 
13
8
  const resolutionCache = new Map<string, Promise<string | undefined>>();
14
9
  const logger = authLogger("cli");
15
10
 
16
- interface DatabricksCliAsset {
17
- readonly url: string;
18
- readonly sha256: string;
19
- }
20
-
21
11
  /** Deferred Databricks CLI lookup used by U2M token acquisition. */
22
12
  export type DatabricksCliResolver = () => Promise<string | undefined>;
23
13
 
24
- /** Controls whether CLI resolution may install the pinned managed executable. */
25
- export interface DatabricksCliResolutionOptions {
26
- install?: boolean;
27
- }
28
-
29
- /** Resolve a compatible Databricks CLI, installing the pinned build asset when needed. */
14
+ /** Resolve an available Databricks CLI without installing executables. */
30
15
  export function resolveDatabricksCli(
31
16
  environment: Readonly<Record<string, string | undefined>> = process.env,
32
- options: DatabricksCliResolutionOptions = {},
33
17
  ): Promise<string | undefined> {
34
18
  const candidate = stringValue(environment.DATABRICKS_CLI_PATH) ?? "databricks";
35
- const key = `${candidate}\0${homedir()}\0${process.platform}\0${process.arch}\0${options.install !== false}`;
36
- const cached = resolutionCache.get(key);
19
+ const cached = resolutionCache.get(candidate);
37
20
  if (cached) {
38
- logger.debug("reused Databricks CLI resolution", {
39
- candidate,
40
- install: options.install !== false,
41
- });
21
+ logger.debug("reused Databricks CLI resolution", { candidate });
42
22
  return cached;
43
23
  }
44
- logger.debug("resolving Databricks CLI", {
45
- candidate,
46
- install: options.install !== false,
47
- platform: process.platform,
48
- arch: process.arch,
49
- minimumVersion: cliAssets.minimumVersion,
50
- });
51
- const resolved = resolveDatabricksCliUncached(candidate, options.install !== false).catch(
52
- (error) => {
53
- resolutionCache.delete(key);
24
+ logger.debug("resolving Databricks CLI", { candidate });
25
+ const resolved = compatibleDatabricksCli(candidate)
26
+ .then((compatible) => (compatible ? candidate : undefined))
27
+ .catch((error) => {
28
+ resolutionCache.delete(candidate);
54
29
  throw error;
55
- },
56
- );
57
- resolutionCache.set(key, resolved);
30
+ });
31
+ resolutionCache.set(candidate, resolved);
58
32
  return resolved;
59
33
  }
60
34
 
@@ -64,59 +38,6 @@ export function resetDatabricksCliResolution(): void {
64
38
  logger.debug("cleared Databricks CLI resolution cache");
65
39
  }
66
40
 
67
- async function resolveDatabricksCliUncached(
68
- candidate: string,
69
- install: boolean,
70
- ): Promise<string | undefined> {
71
- if (await compatibleDatabricksCli(candidate)) {
72
- logger.debug("selected installed Databricks CLI", { executable: candidate });
73
- return candidate;
74
- }
75
- const managed = managedExecutable();
76
- if (await compatibleDatabricksCli(managed)) {
77
- logger.debug("selected managed Databricks CLI", { executable: managed });
78
- return managed;
79
- }
80
- if (!install) {
81
- logger.debug("compatible Databricks CLI unavailable and installation disabled");
82
- return undefined;
83
- }
84
- const asset = platformAsset();
85
- if (!asset) {
86
- logger.debug("no managed Databricks CLI asset for platform", {
87
- platform: process.platform,
88
- arch: process.arch,
89
- });
90
- return undefined;
91
- }
92
- const root = join(homedir(), ".databricks");
93
- const binDir = join(root, "bin");
94
- const executable = managedExecutableName();
95
- const installed = await bin.ensure("databricks", asset, {
96
- autoUnpackage: true,
97
- destination: { root, binDir, path: join(binDir, executable) },
98
- minVersion: cliAssets.minimumVersion,
99
- selector: ({ source }) => join(source, executable),
100
- versionParser: (output) => {
101
- const version = bin.parseVersion(output);
102
- return version === cliAssets.version ? version : undefined;
103
- },
104
- });
105
- logger.debug("ensured managed Databricks CLI", {
106
- executable: installed.path,
107
- version: cliAssets.version,
108
- });
109
- return installed.path;
110
- }
111
-
112
- function managedExecutable(): string {
113
- return join(homedir(), ".databricks", "bin", managedExecutableName());
114
- }
115
-
116
- function managedExecutableName(): string {
117
- return process.platform === "win32" ? "databricks.exe" : "databricks";
118
- }
119
-
120
41
  async function compatibleDatabricksCli(executable: string): Promise<boolean> {
121
42
  try {
122
43
  const result = await runProcess(executable, ["--version"], {}, 10_000);
@@ -127,16 +48,8 @@ async function compatibleDatabricksCli(executable: string): Promise<boolean> {
127
48
  });
128
49
  return false;
129
50
  }
130
- const version = bin.parseVersion(result);
131
- const compatible =
132
- version !== undefined && bin.isVersionAtLeast(version, cliAssets.minimumVersion);
133
- logger.debug("probed Databricks CLI version", {
134
- executable,
135
- version: version ?? "unknown",
136
- minimumVersion: cliAssets.minimumVersion,
137
- compatible,
138
- });
139
- return compatible;
51
+ logger.debug("selected installed Databricks CLI", { executable });
52
+ return true;
140
53
  } catch (cause) {
141
54
  logger.debug("Databricks CLI version probe unavailable", {
142
55
  executable,
@@ -146,11 +59,6 @@ async function compatibleDatabricksCli(executable: string): Promise<boolean> {
146
59
  }
147
60
  }
148
61
 
149
- function platformAsset(): DatabricksCliAsset | undefined {
150
- const key = `${process.platform}-${process.arch}` as keyof typeof cliAssets.assets;
151
- return cliAssets.assets[key];
152
- }
153
-
154
62
  /** Run interactive Databricks CLI login for one profile. */
155
63
  export async function databricksCliLogin(
156
64
  profile: string,
@@ -164,8 +72,9 @@ export async function databricksCliLogin(
164
72
  ["auth", "login", "--profile", profile, "--timeout", `${Math.ceil(timeoutMs / 1000)}s`],
165
73
  environment,
166
74
  );
167
- if (result.exitCode !== 0)
75
+ if (result.exitCode !== 0) {
168
76
  throw new AuthError("cli", result.stderr || `databricks auth login exited ${result.exitCode}`);
77
+ }
169
78
  logger.debug("Databricks CLI login completed", { profile, executable });
170
79
  }
171
80
 
@@ -180,8 +89,9 @@ export async function databricksCliToken(
180
89
  const args = ["auth", "token", "--profile", profile, "--output", "json"];
181
90
  if (forceRefresh) args.push("--force-refresh");
182
91
  const result = await runProcess(executable, args, environment);
183
- if (result.exitCode !== 0)
92
+ if (result.exitCode !== 0) {
184
93
  throw new AuthError("cli", result.stderr || `databricks auth token exited ${result.exitCode}`);
94
+ }
185
95
  let value: Record<string, unknown>;
186
96
  try {
187
97
  value = JSON.parse(result.stdout ?? "") as Record<string, unknown>;
@@ -281,8 +191,9 @@ export class DatabricksCliProvider implements TokenProvider {
281
191
  logger.debug("resolving provider CLI lazily", { profile: this.profile });
282
192
  this.resolution = this.executableOrResolver()
283
193
  .then((executable) => {
284
- if (!executable)
194
+ if (!executable) {
285
195
  throw new AuthError("cli", "Databricks CLI is unavailable on this platform");
196
+ }
286
197
  return executable;
287
198
  })
288
199
  .catch((error) => {
package/src/_errors.ts ADDED
@@ -0,0 +1,11 @@
1
+ /** Authentication failure with a stable category for callers and language shims. */
2
+ export class AuthError extends Error {
3
+ constructor(
4
+ readonly kind: "config" | "oauth" | "cli",
5
+ message: string,
6
+ options?: ErrorOptions,
7
+ ) {
8
+ super(message, options);
9
+ this.name = "AuthError";
10
+ }
11
+ }
@@ -1,6 +1,8 @@
1
- import { AuthError } from "./errors.ts";
1
+ import { AuthError } from "./_errors.ts";
2
2
  import { authLogger, credentialId, failureMetadata, tokenMetadata } from "./_logging.ts";
3
- import type { AccessToken, AuthOptions, CredentialStore, Token, TokenProvider } from "./types.ts";
3
+ import type { CredentialStore, Token, TokenProvider } from "./_types.ts";
4
+ import type { AccessToken } from "./client.ts";
5
+ import type { AuthOptions } from "./config.ts";
4
6
 
5
7
  const logger = authLogger("lifecycle");
6
8
 
@@ -10,30 +12,22 @@ export class TokenLifecycle {
10
12
  private readonly key: string,
11
13
  private readonly provider: TokenProvider,
12
14
  private readonly store: CredentialStore,
13
- private readonly options: AuthOptions,
15
+ private readonly options: Required<AuthOptions>,
14
16
  private readonly now: () => Date = () => new Date(),
15
17
  ) {
16
18
  logger.debug("created authentication lifecycle", {
17
19
  credential: credentialId(key),
18
- storage: store.name(),
19
- refreshBufferSeconds: options.refreshBufferSeconds,
20
- lockTimeoutSeconds: options.lockTimeoutSeconds,
21
- loginTimeoutSeconds: options.loginTimeoutSeconds,
20
+ refreshBufferMs: options.refreshBufferMs,
21
+ lockTimeoutMs: options.lockTimeoutMs,
22
+ loginTimeoutMs: options.loginTimeoutMs,
22
23
  silentProvider: provider.canAuthenticateSilently(),
23
24
  });
24
25
  }
25
26
 
26
- storeName(): string {
27
- return this.store.name();
28
- }
29
-
30
27
  async login(): Promise<AccessToken> {
31
28
  logger.debug("interactive login requested", this.context());
32
29
  return this.withLock(async () => {
33
- await this.store.prepareWrite();
34
- const token = validateToken(
35
- await this.provider.login(this.options.loginTimeoutSeconds * 1000),
36
- );
30
+ const token = validateToken(await this.provider.login(this.options.loginTimeoutMs));
37
31
  await this.store.save(this.key, token);
38
32
  logger.debug("interactive login stored credential", {
39
33
  ...this.context(),
@@ -60,12 +54,7 @@ export class TokenLifecycle {
60
54
 
61
55
  forceRefresh(login = true): Promise<AccessToken> {
62
56
  logger.debug("forced refresh requested", { ...this.context(), login });
63
- return this.refreshRejected(undefined, login);
64
- }
65
-
66
- refreshRejectedToken(staleAccessToken: string, login = true): Promise<AccessToken> {
67
- logger.debug("rejected token refresh requested", { ...this.context(), login });
68
- return this.refreshRejected(staleAccessToken, login);
57
+ return this.refresh(login);
69
58
  }
70
59
 
71
60
  async logout(): Promise<void> {
@@ -97,24 +86,9 @@ export class TokenLifecycle {
97
86
  });
98
87
  }
99
88
 
100
- private async refreshRejected(
101
- staleAccessToken: string | undefined,
102
- login: boolean,
103
- ): Promise<AccessToken> {
89
+ private async refresh(login: boolean): Promise<AccessToken> {
104
90
  return this.withLock(async () => {
105
91
  const current = await this.store.load(this.key);
106
- if (
107
- staleAccessToken &&
108
- current?.accessToken &&
109
- current.accessToken !== staleAccessToken &&
110
- isValid(current, this.now())
111
- ) {
112
- logger.debug("reused replacement for rejected credential", {
113
- ...this.context(),
114
- token: tokenMetadata(current, this.now()),
115
- });
116
- return publicToken(current);
117
- }
118
92
  return this.renew(current, login);
119
93
  });
120
94
  }
@@ -137,7 +111,7 @@ export class TokenLifecycle {
137
111
  });
138
112
  if (!login) throw error;
139
113
  logger.debug("falling back to interactive login", this.context());
140
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
114
+ token = await this.provider.login(this.options.loginTimeoutMs);
141
115
  }
142
116
  } else if (this.provider.canAuthenticateSilently()) {
143
117
  logger.debug("attempting silent credential acquisition", {
@@ -145,7 +119,7 @@ export class TokenLifecycle {
145
119
  loginFallback: login,
146
120
  });
147
121
  try {
148
- token = await this.provider.authenticate(this.options.loginTimeoutSeconds * 1000);
122
+ token = await this.provider.authenticate(this.options.loginTimeoutMs);
149
123
  } catch (error) {
150
124
  logger.debug("silent credential acquisition failed", {
151
125
  ...this.context(),
@@ -154,11 +128,11 @@ export class TokenLifecycle {
154
128
  });
155
129
  if (!login) throw error;
156
130
  logger.debug("falling back to interactive login", this.context());
157
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
131
+ token = await this.provider.login(this.options.loginTimeoutMs);
158
132
  }
159
133
  } else if (login) {
160
134
  logger.debug("provider requires interactive login", this.context());
161
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
135
+ token = await this.provider.login(this.options.loginTimeoutMs);
162
136
  } else {
163
137
  throw new AuthError(
164
138
  "oauth",
@@ -170,7 +144,6 @@ export class TokenLifecycle {
170
144
  ...this.context(),
171
145
  token: tokenMetadata(token, this.now()),
172
146
  });
173
- await this.store.prepareWrite();
174
147
  await this.store.save(this.key, token);
175
148
  logger.debug("credential saved", this.context());
176
149
  return publicToken(token);
@@ -179,14 +152,16 @@ export class TokenLifecycle {
179
152
  private canReuse(token: Token): boolean {
180
153
  if (!isValid(token, this.now())) return false;
181
154
  if (!token.expiry) return true;
182
- return (
183
- Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferSeconds * 1000
184
- );
155
+ return Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferMs;
185
156
  }
186
157
 
187
158
  private async withLock<T>(action: () => Promise<T>): Promise<T> {
188
159
  logger.debug("waiting for credential lock", this.context());
189
- const lease = await this.store.acquireLock(this.key, this.options.lockTimeoutSeconds * 1000);
160
+ // Omit the timeout (wait indefinitely) unless a positive cap is configured.
161
+ // The holder may run for minutes; the caller owns the overall request budget
162
+ // and the lock is always released when `action` settles (success or error).
163
+ const lockTimeoutMs = this.options.lockTimeoutMs > 0 ? this.options.lockTimeoutMs : undefined;
164
+ const lease = await this.store.acquireLock(this.key, lockTimeoutMs);
190
165
  logger.debug("credential lock acquired", this.context());
191
166
  let failure: unknown;
192
167
  try {
@@ -209,7 +184,7 @@ export class TokenLifecycle {
209
184
  }
210
185
 
211
186
  private context(): Record<string, unknown> {
212
- return { credential: credentialId(this.key), storage: this.store.name() };
187
+ return { credential: credentialId(this.key) };
213
188
  }
214
189
  }
215
190
 
@@ -223,8 +198,9 @@ export function publicToken(token: Token): AccessToken {
223
198
  }
224
199
 
225
200
  export function validateToken(token: Token, previous?: Token): Token {
226
- if (!token.accessToken.trim())
201
+ if (!token.accessToken.trim()) {
227
202
  throw new AuthError("oauth", "Token response did not contain an access token");
203
+ }
228
204
  return {
229
205
  accessToken: token.accessToken,
230
206
  tokenType: token.tokenType || "Bearer",
package/src/_logging.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as hash from "@dbx-tools/shared-core/hash";
2
2
  import * as log from "@dbx-tools/shared-core/log";
3
3
 
4
- import { AuthError } from "./errors.ts";
5
- import type { Token } from "./types.ts";
4
+ import { AuthError } from "./_errors.ts";
5
+ import type { Token } from "./_types.ts";
6
6
 
7
7
  /** Auth logger with a stable capability-specific tag. */
8
8
  export function authLogger(scope: string): log.Logger {
@@ -1,5 +1,5 @@
1
1
  import { authLogger } from "./_logging.ts";
2
- import type { Token, TokenProvider } from "./types.ts";
2
+ import type { Token, TokenProvider } from "./_types.ts";
3
3
 
4
4
  const logger = authLogger("pat");
5
5