@vunexa/lixa 0.1.3 → 0.1.6-alpha.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/README.md CHANGED
@@ -28,7 +28,36 @@ A flexible, provider-agnostic OAuth 2.0 and OpenID Connect (OIDC) client library
28
28
 
29
29
  ## Architecture Overview
30
30
 
31
- ![Architecture Overview](https://cdn.jsdelivr.net/npm/@vunexa/lixa/docs/images/architecture.svg)
31
+ ```mermaid
32
+ graph TD
33
+ User([User Client / Browser])
34
+
35
+ subgraph Lixa ["Lixa Core Engine"]
36
+ AuthNGuard["Primary AuthN Guard<br/>(Minimal Identity Scopes Only)"]
37
+ SessionMgr["Session Handler & Unified Session"]
38
+ AccountLinker["Account Linker<br/>(AUTO_LINK_BY_VERIFIED_EMAIL)"]
39
+ ResourceConn["Resource Connection API<br/>(Post-Login AuthZ)"]
40
+ end
41
+
42
+ subgraph IdentityProviders ["Identity Providers (AuthN)"]
43
+ GoogleAuth["Google / OIDC"]
44
+ GitHubAuth["GitHub OAuth"]
45
+ end
46
+
47
+ subgraph ResourceProviders ["Resource APIs (AuthZ)"]
48
+ GitHubAPI["GitHub API (repos, orgs)"]
49
+ GoogleDriveAPI["Google Drive API"]
50
+ end
51
+
52
+ User -->|"1. Primary Login / Link Account"| AuthNGuard
53
+ AuthNGuard -->|"Request Identity"| IdentityProviders
54
+ IdentityProviders -->|"Tokens + UserInfo"| AccountLinker
55
+ AccountLinker -->|"Unified Session (session.accounts)"| SessionMgr
56
+
57
+ User -->|"2. Connect Resource API (Post-Login)"| ResourceConn
58
+ ResourceConn -->|"Request Permissions (repo, drive)"| ResourceProviders
59
+ ResourceProviders -->|"Resource Access Tokens"| SessionMgr
60
+ ```
32
61
 
33
62
  ---
34
63
 
@@ -252,6 +281,277 @@ export interface Session<TRaw = OAuthTokenResponse> {
252
281
 
253
282
  ---
254
283
 
284
+ ## Custom Session Storage Implementations
285
+
286
+ Lixa allows you to store sessions in any database or cache by implementing the `SessionStorage` interface.
287
+
288
+ ### 1. SQLite Session Storage (`better-sqlite3`)
289
+
290
+ For relational persistence or single-node deployments using SQLite:
291
+
292
+ #### Table Schema (SQL DDL)
293
+
294
+ ```sql
295
+ CREATE TABLE IF NOT EXISTS sessions (
296
+ id TEXT PRIMARY KEY,
297
+ user_email TEXT,
298
+ data TEXT NOT NULL,
299
+ expires_at INTEGER NOT NULL
300
+ );
301
+
302
+ CREATE INDEX IF NOT EXISTS idx_sessions_email ON sessions(user_email);
303
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
304
+ ```
305
+
306
+ #### TypeScript Implementation
307
+
308
+ ```typescript
309
+ import Database from "better-sqlite3";
310
+ import { Lixa, type SessionStorage, type Session } from "@vunexa/lixa";
311
+
312
+ export class SqliteSessionStorage implements SessionStorage {
313
+ private db = new Database("lixa_sessions.db");
314
+
315
+ constructor() {
316
+ this.db.exec(`
317
+ CREATE TABLE IF NOT EXISTS sessions (
318
+ id TEXT PRIMARY KEY,
319
+ user_email TEXT,
320
+ data TEXT NOT NULL,
321
+ expires_at INTEGER NOT NULL
322
+ );
323
+ CREATE INDEX IF NOT EXISTS idx_sessions_email ON sessions(user_email);
324
+ `);
325
+ }
326
+
327
+ async saveSession<T extends Session>(sessionId: string, session: T, expiresInSeconds: number): Promise<void> {
328
+ const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;
329
+ const stmt = this.db.prepare(`
330
+ INSERT INTO sessions (id, user_email, data, expires_at)
331
+ VALUES (?, ?, ?, ?)
332
+ ON CONFLICT(id) DO UPDATE SET
333
+ user_email = excluded.user_email,
334
+ data = excluded.data,
335
+ expires_at = excluded.expires_at
336
+ `);
337
+ stmt.run(sessionId, session.email || null, JSON.stringify(session), expiresAt);
338
+ }
339
+
340
+ async getSession<T extends Session>(sessionId: string): Promise<T | null> {
341
+ const now = Math.floor(Date.now() / 1000);
342
+ const stmt = this.db.prepare(`SELECT data FROM sessions WHERE id = ? AND expires_at > ?`);
343
+ const row = stmt.get(sessionId, now) as { data: string } | undefined;
344
+ return row ? (JSON.parse(row.data) as T) : null;
345
+ }
346
+
347
+ async deleteSession(sessionId: string): Promise<void> {
348
+ const stmt = this.db.prepare(`DELETE FROM sessions WHERE id = ?`);
349
+ stmt.run(sessionId);
350
+ }
351
+
352
+ async getSessionByEmail<T extends Session>(email: string): Promise<{ sessionId: string; session: T } | null> {
353
+ const now = Math.floor(Date.now() / 1000);
354
+ const stmt = this.db.prepare(`SELECT id, data FROM sessions WHERE user_email = ? AND expires_at > ? LIMIT 1`);
355
+ const row = stmt.get(email, now) as { id: string; data: string } | undefined;
356
+ return row ? { sessionId: row.id, session: JSON.parse(row.data) as T } : null;
357
+ }
358
+ }
359
+
360
+ // Pass to Lixa instance
361
+ export const lixa = new Lixa({
362
+ sessionHandler: {
363
+ sessionStorage: new SqliteSessionStorage(),
364
+ },
365
+ providers: { /* ... */ },
366
+ });
367
+ ```
368
+
369
+ #### Saved JSON Record Example in SQLite (`data` Column)
370
+
371
+ ```json
372
+ {
373
+ "id": "e4a91f82c3b4a07f",
374
+ "token": "ya29.a0ARW5m7...",
375
+ "userId": "1049281048",
376
+ "email": "alex.developer@example.com",
377
+ "provider": "google",
378
+ "accounts": {
379
+ "google": {
380
+ "provider": "google",
381
+ "email": "alex.developer@example.com",
382
+ "providerUserId": "1049281048",
383
+ "accessToken": "ya29.a0ARW5m7...",
384
+ "linkedAt": 1771657200000
385
+ },
386
+ "github": {
387
+ "provider": "github",
388
+ "email": "alex.developer@example.com",
389
+ "providerUserId": "5829104",
390
+ "accessToken": "gho_8f7b2a9e1c3...",
391
+ "linkedAt": 1771657250000
392
+ }
393
+ },
394
+ "resources": {
395
+ "github": {
396
+ "provider": "github",
397
+ "accessToken": "gho_resource_repo_9a8b7c...",
398
+ "scopes": ["repo", "read:org"],
399
+ "connectedAt": 1771657300000
400
+ }
401
+ },
402
+ "raw": {
403
+ "access_token": "ya29.a0ARW5m7...",
404
+ "token_type": "Bearer",
405
+ "expires_in": 3599
406
+ }
407
+ }
408
+ ```
409
+
410
+ ---
411
+
412
+ ### 2. AWS DynamoDB Session Storage (`@aws-sdk/lib-dynamodb`)
413
+
414
+ For serverless and distributed AWS deployments using DynamoDB:
415
+
416
+ #### Table Configuration
417
+
418
+ - **Table Name**: `LixaSessions`
419
+ - **Partition Key**: `sessionId` (String)
420
+ - **Global Secondary Index (GSI)**: `EmailIndex` (`email` as Partition Key)
421
+ - **TTL Attribute**: `ttl` (Unix timestamp in seconds for automatic AWS expiration)
422
+
423
+ #### TypeScript Implementation
424
+
425
+ ```typescript
426
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
427
+ import { DynamoDBDocumentClient, PutCommand, GetCommand, DeleteCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";
428
+ import { Lixa, type SessionStorage, type Session } from "@vunexa/lixa";
429
+
430
+ export class DynamoDbSessionStorage implements SessionStorage {
431
+ private docClient: DynamoDBDocumentClient;
432
+ private tableName = "LixaSessions";
433
+
434
+ constructor() {
435
+ const client = new DynamoDBClient({ region: process.env.AWS_REGION || "us-east-1" });
436
+ this.docClient = DynamoDBDocumentClient.from(client);
437
+ }
438
+
439
+ async saveSession<T extends Session>(sessionId: string, session: T, expiresInSeconds: number): Promise<void> {
440
+ const ttl = Math.floor(Date.now() / 1000) + expiresInSeconds;
441
+ await this.docClient.send(
442
+ new PutCommand({
443
+ TableName: this.tableName,
444
+ Item: {
445
+ sessionId,
446
+ email: session.email || "N/A",
447
+ sessionData: session,
448
+ ttl,
449
+ },
450
+ })
451
+ );
452
+ }
453
+
454
+ async getSession<T extends Session>(sessionId: string): Promise<T | null> {
455
+ const res = await this.docClient.send(
456
+ new GetCommand({
457
+ TableName: this.tableName,
458
+ Key: { sessionId },
459
+ })
460
+ );
461
+
462
+ if (!res.Item) return null;
463
+ const now = Math.floor(Date.now() / 1000);
464
+ if (res.Item.ttl && res.Item.ttl < now) return null;
465
+
466
+ return res.Item.sessionData as T;
467
+ }
468
+
469
+ async deleteSession(sessionId: string): Promise<void> {
470
+ await this.docClient.send(
471
+ new DeleteCommand({
472
+ TableName: this.tableName,
473
+ Key: { sessionId },
474
+ })
475
+ );
476
+ }
477
+
478
+ async getSessionByEmail<T extends Session>(email: string): Promise<{ sessionId: string; session: T } | null> {
479
+ const res = await this.docClient.send(
480
+ new QueryCommand({
481
+ TableName: this.tableName,
482
+ IndexName: "EmailIndex",
483
+ KeyConditionExpression: "email = :email",
484
+ ExpressionAttributeValues: { ":email": email },
485
+ Limit: 1,
486
+ })
487
+ );
488
+
489
+ if (!res.Items || res.Items.length === 0) return null;
490
+ const item = res.Items[0];
491
+ const now = Math.floor(Date.now() / 1000);
492
+ if (item.ttl && item.ttl < now) return null;
493
+
494
+ return { sessionId: item.sessionId, session: item.sessionData as T };
495
+ }
496
+ }
497
+
498
+ // Pass to Lixa instance
499
+ export const lixa = new Lixa({
500
+ sessionHandler: {
501
+ sessionStorage: new DynamoDbSessionStorage(),
502
+ },
503
+ providers: { /* ... */ },
504
+ });
505
+ ```
506
+
507
+ #### Saved DynamoDB Item JSON Example
508
+
509
+ ```json
510
+ {
511
+ "sessionId": "e4a91f82c3b4a07f",
512
+ "email": "alex.developer@example.com",
513
+ "ttl": 1771743600,
514
+ "sessionData": {
515
+ "id": "e4a91f82c3b4a07f",
516
+ "token": "ya29.a0ARW5m7...",
517
+ "userId": "1049281048",
518
+ "email": "alex.developer@example.com",
519
+ "provider": "google",
520
+ "accounts": {
521
+ "google": {
522
+ "provider": "google",
523
+ "email": "alex.developer@example.com",
524
+ "providerUserId": "1049281048",
525
+ "accessToken": "ya29.a0ARW5m7...",
526
+ "linkedAt": 1771657200000
527
+ },
528
+ "github": {
529
+ "provider": "github",
530
+ "email": "alex.developer@example.com",
531
+ "providerUserId": "5829104",
532
+ "accessToken": "gho_8f7b2a9e1c3...",
533
+ "linkedAt": 1771657250000
534
+ }
535
+ },
536
+ "resources": {
537
+ "github": {
538
+ "provider": "github",
539
+ "accessToken": "gho_resource_repo_9a8b7c...",
540
+ "scopes": ["repo", "read:org"],
541
+ "connectedAt": 1771657300000
542
+ }
543
+ },
544
+ "raw": {
545
+ "access_token": "ya29.a0ARW5m7...",
546
+ "token_type": "Bearer",
547
+ "expires_in": 3599
548
+ }
549
+ }
550
+ }
551
+ ```
552
+
553
+ ---
554
+
255
555
  ## User Info Utilities
256
556
 
257
557
  Lixa provides utilities to extract user information from OAuth tokens:
@@ -342,6 +342,277 @@ export interface Session<TRaw = OAuthTokenResponse> {
342
342
 
343
343
  ---
344
344
 
345
+ ## Custom Session Storage Implementations
346
+
347
+ Lixa allows you to store sessions in any database or cache by implementing the `SessionStorage` interface.
348
+
349
+ ### 1. SQLite Session Storage (`better-sqlite3`)
350
+
351
+ For relational persistence or single-node deployments using SQLite:
352
+
353
+ #### Table Schema (SQL DDL)
354
+
355
+ ```sql
356
+ CREATE TABLE IF NOT EXISTS sessions (
357
+ id TEXT PRIMARY KEY,
358
+ user_email TEXT,
359
+ data TEXT NOT NULL,
360
+ expires_at INTEGER NOT NULL
361
+ );
362
+
363
+ CREATE INDEX IF NOT EXISTS idx_sessions_email ON sessions(user_email);
364
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
365
+ ```
366
+
367
+ #### TypeScript Implementation
368
+
369
+ ```typescript
370
+ import Database from "better-sqlite3";
371
+ import { Lixa, type SessionStorage, type Session } from "@vunexa/lixa";
372
+
373
+ export class SqliteSessionStorage implements SessionStorage {
374
+ private db = new Database("lixa_sessions.db");
375
+
376
+ constructor() {
377
+ this.db.exec(`
378
+ CREATE TABLE IF NOT EXISTS sessions (
379
+ id TEXT PRIMARY KEY,
380
+ user_email TEXT,
381
+ data TEXT NOT NULL,
382
+ expires_at INTEGER NOT NULL
383
+ );
384
+ CREATE INDEX IF NOT EXISTS idx_sessions_email ON sessions(user_email);
385
+ `);
386
+ }
387
+
388
+ async saveSession<T extends Session>(sessionId: string, session: T, expiresInSeconds: number): Promise<void> {
389
+ const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;
390
+ const stmt = this.db.prepare(`
391
+ INSERT INTO sessions (id, user_email, data, expires_at)
392
+ VALUES (?, ?, ?, ?)
393
+ ON CONFLICT(id) DO UPDATE SET
394
+ user_email = excluded.user_email,
395
+ data = excluded.data,
396
+ expires_at = excluded.expires_at
397
+ `);
398
+ stmt.run(sessionId, session.email || null, JSON.stringify(session), expiresAt);
399
+ }
400
+
401
+ async getSession<T extends Session>(sessionId: string): Promise<T | null> {
402
+ const now = Math.floor(Date.now() / 1000);
403
+ const stmt = this.db.prepare(`SELECT data FROM sessions WHERE id = ? AND expires_at > ?`);
404
+ const row = stmt.get(sessionId, now) as { data: string } | undefined;
405
+ return row ? (JSON.parse(row.data) as T) : null;
406
+ }
407
+
408
+ async deleteSession(sessionId: string): Promise<void> {
409
+ const stmt = this.db.prepare(`DELETE FROM sessions WHERE id = ?`);
410
+ stmt.run(sessionId);
411
+ }
412
+
413
+ async getSessionByEmail<T extends Session>(email: string): Promise<{ sessionId: string; session: T } | null> {
414
+ const now = Math.floor(Date.now() / 1000);
415
+ const stmt = this.db.prepare(`SELECT id, data FROM sessions WHERE user_email = ? AND expires_at > ? LIMIT 1`);
416
+ const row = stmt.get(email, now) as { id: string; data: string } | undefined;
417
+ return row ? { sessionId: row.id, session: JSON.parse(row.data) as T } : null;
418
+ }
419
+ }
420
+
421
+ // Pass to Lixa instance
422
+ export const lixa = new Lixa({
423
+ sessionHandler: {
424
+ sessionStorage: new SqliteSessionStorage(),
425
+ },
426
+ providers: { /* ... */ },
427
+ });
428
+ ```
429
+
430
+ #### Saved JSON Record Example in SQLite (`data` Column)
431
+
432
+ ```json
433
+ {
434
+ "id": "e4a91f82c3b4a07f",
435
+ "token": "ya29.a0ARW5m7...",
436
+ "userId": "1049281048",
437
+ "email": "alex.developer@example.com",
438
+ "provider": "google",
439
+ "accounts": {
440
+ "google": {
441
+ "provider": "google",
442
+ "email": "alex.developer@example.com",
443
+ "providerUserId": "1049281048",
444
+ "accessToken": "ya29.a0ARW5m7...",
445
+ "linkedAt": 1771657200000
446
+ },
447
+ "github": {
448
+ "provider": "github",
449
+ "email": "alex.developer@example.com",
450
+ "providerUserId": "5829104",
451
+ "accessToken": "gho_8f7b2a9e1c3...",
452
+ "linkedAt": 1771657250000
453
+ }
454
+ },
455
+ "resources": {
456
+ "github": {
457
+ "provider": "github",
458
+ "accessToken": "gho_resource_repo_9a8b7c...",
459
+ "scopes": ["repo", "read:org"],
460
+ "connectedAt": 1771657300000
461
+ }
462
+ },
463
+ "raw": {
464
+ "access_token": "ya29.a0ARW5m7...",
465
+ "token_type": "Bearer",
466
+ "expires_in": 3599
467
+ }
468
+ }
469
+ ```
470
+
471
+ ---
472
+
473
+ ### 2. AWS DynamoDB Session Storage (`@aws-sdk/lib-dynamodb`)
474
+
475
+ For serverless and distributed AWS deployments using DynamoDB:
476
+
477
+ #### Table Configuration
478
+
479
+ - **Table Name**: `LixaSessions`
480
+ - **Partition Key**: `sessionId` (String)
481
+ - **Global Secondary Index (GSI)**: `EmailIndex` (`email` as Partition Key)
482
+ - **TTL Attribute**: `ttl` (Unix timestamp in seconds for automatic AWS expiration)
483
+
484
+ #### TypeScript Implementation
485
+
486
+ ```typescript
487
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
488
+ import { DynamoDBDocumentClient, PutCommand, GetCommand, DeleteCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";
489
+ import { Lixa, type SessionStorage, type Session } from "@vunexa/lixa";
490
+
491
+ export class DynamoDbSessionStorage implements SessionStorage {
492
+ private docClient: DynamoDBDocumentClient;
493
+ private tableName = "LixaSessions";
494
+
495
+ constructor() {
496
+ const client = new DynamoDBClient({ region: process.env.AWS_REGION || "us-east-1" });
497
+ this.docClient = DynamoDBDocumentClient.from(client);
498
+ }
499
+
500
+ async saveSession<T extends Session>(sessionId: string, session: T, expiresInSeconds: number): Promise<void> {
501
+ const ttl = Math.floor(Date.now() / 1000) + expiresInSeconds;
502
+ await this.docClient.send(
503
+ new PutCommand({
504
+ TableName: this.tableName,
505
+ Item: {
506
+ sessionId,
507
+ email: session.email || "N/A",
508
+ sessionData: session,
509
+ ttl,
510
+ },
511
+ })
512
+ );
513
+ }
514
+
515
+ async getSession<T extends Session>(sessionId: string): Promise<T | null> {
516
+ const res = await this.docClient.send(
517
+ new GetCommand({
518
+ TableName: this.tableName,
519
+ Key: { sessionId },
520
+ })
521
+ );
522
+
523
+ if (!res.Item) return null;
524
+ const now = Math.floor(Date.now() / 1000);
525
+ if (res.Item.ttl && res.Item.ttl < now) return null;
526
+
527
+ return res.Item.sessionData as T;
528
+ }
529
+
530
+ async deleteSession(sessionId: string): Promise<void> {
531
+ await this.docClient.send(
532
+ new DeleteCommand({
533
+ TableName: this.tableName,
534
+ Key: { sessionId },
535
+ })
536
+ );
537
+ }
538
+
539
+ async getSessionByEmail<T extends Session>(email: string): Promise<{ sessionId: string; session: T } | null> {
540
+ const res = await this.docClient.send(
541
+ new QueryCommand({
542
+ TableName: this.tableName,
543
+ IndexName: "EmailIndex",
544
+ KeyConditionExpression: "email = :email",
545
+ ExpressionAttributeValues: { ":email": email },
546
+ Limit: 1,
547
+ })
548
+ );
549
+
550
+ if (!res.Items || res.Items.length === 0) return null;
551
+ const item = res.Items[0];
552
+ const now = Math.floor(Date.now() / 1000);
553
+ if (item.ttl && item.ttl < now) return null;
554
+
555
+ return { sessionId: item.sessionId, session: item.sessionData as T };
556
+ }
557
+ }
558
+
559
+ // Pass to Lixa instance
560
+ export const lixa = new Lixa({
561
+ sessionHandler: {
562
+ sessionStorage: new DynamoDbSessionStorage(),
563
+ },
564
+ providers: { /* ... */ },
565
+ });
566
+ ```
567
+
568
+ #### Saved DynamoDB Item JSON Example
569
+
570
+ ```json
571
+ {
572
+ "sessionId": "e4a91f82c3b4a07f",
573
+ "email": "alex.developer@example.com",
574
+ "ttl": 1771743600,
575
+ "sessionData": {
576
+ "id": "e4a91f82c3b4a07f",
577
+ "token": "ya29.a0ARW5m7...",
578
+ "userId": "1049281048",
579
+ "email": "alex.developer@example.com",
580
+ "provider": "google",
581
+ "accounts": {
582
+ "google": {
583
+ "provider": "google",
584
+ "email": "alex.developer@example.com",
585
+ "providerUserId": "1049281048",
586
+ "accessToken": "ya29.a0ARW5m7...",
587
+ "linkedAt": 1771657200000
588
+ },
589
+ "github": {
590
+ "provider": "github",
591
+ "email": "alex.developer@example.com",
592
+ "providerUserId": "5829104",
593
+ "accessToken": "gho_8f7b2a9e1c3...",
594
+ "linkedAt": 1771657250000
595
+ }
596
+ },
597
+ "resources": {
598
+ "github": {
599
+ "provider": "github",
600
+ "accessToken": "gho_resource_repo_9a8b7c...",
601
+ "scopes": ["repo", "read:org"],
602
+ "connectedAt": 1771657300000
603
+ }
604
+ },
605
+ "raw": {
606
+ "access_token": "ya29.a0ARW5m7...",
607
+ "token_type": "Bearer",
608
+ "expires_in": 3599
609
+ }
610
+ }
611
+ }
612
+ ```
613
+
614
+ ---
615
+
345
616
  ## User Info Utilities
346
617
 
347
618
  Lixa provides utilities to extract user information from OAuth tokens:
@@ -740,6 +740,8 @@ export declare class Lixa<TConfig extends LixaConfig<Record<string, ProviderConf
740
740
  provider: ConfiguredProviderKey<TConfig> | string;
741
741
  scopes: string[];
742
742
  state?: string;
743
+ prompt?: string;
744
+ extraConfig?: Record<string, string>;
743
745
  }): Promise<string>;
744
746
  /**
745
747
  * Handles the OAuth callback for a connected resource provider and stores resource tokens on the session.
package/dist/index.cjs CHANGED
@@ -869,7 +869,7 @@ var Lixa = class _Lixa {
869
869
  * @returns The authorization URL for resource consent
870
870
  */
871
871
  async getResourceAuthUrl(params) {
872
- const { sessionId, provider, scopes, state } = params;
872
+ const { sessionId, provider, scopes, state, prompt, extraConfig } = params;
873
873
  const sessionStorage = this.sessionHandler.sessionStorage || _Lixa.LOCAL_SESSION_HANDLER.sessionStorage;
874
874
  const activeSession = await sessionStorage.getSession(sessionId);
875
875
  if (!activeSession) {
@@ -902,7 +902,9 @@ var Lixa = class _Lixa {
902
902
  response_type: "code",
903
903
  code_challenge: codeChallenge,
904
904
  code_challenge_method: "S256",
905
- ...providerConfig.extraConfig
905
+ ...prompt ? { prompt } : {},
906
+ ...providerConfig.extraConfig,
907
+ ...extraConfig
906
908
  });
907
909
  return `${providerImpl.authorizationEndpoint}?${searchParams.toString()}`;
908
910
  }
@@ -934,7 +936,7 @@ var Lixa = class _Lixa {
934
936
  await stateStorage.deleteState(state);
935
937
  }
936
938
  }
937
- const tokens = await this.exchangeCodeForToken(providerType, providerConfig, providerImpl, codeVerifier);
939
+ const tokens = await this.exchangeCodeForToken(code, providerConfig, providerImpl, codeVerifier);
938
940
  activeSession.resources = activeSession.resources || {};
939
941
  activeSession.resources[providerType] = {
940
942
  provider: providerType,