@tyravel/auth 0.1.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.
Files changed (81) hide show
  1. package/dist/auth-manager.d.ts +27 -0
  2. package/dist/auth-manager.d.ts.map +1 -0
  3. package/dist/auth-manager.js +102 -0
  4. package/dist/auth-manager.js.map +1 -0
  5. package/dist/auth.test.d.ts +2 -0
  6. package/dist/auth.test.d.ts.map +1 -0
  7. package/dist/auth.test.js +67 -0
  8. package/dist/auth.test.js.map +1 -0
  9. package/dist/authorization-exceptions.d.ts +7 -0
  10. package/dist/authorization-exceptions.d.ts.map +1 -0
  11. package/dist/authorization-exceptions.js +13 -0
  12. package/dist/authorization-exceptions.js.map +1 -0
  13. package/dist/exceptions.d.ts +7 -0
  14. package/dist/exceptions.d.ts.map +1 -0
  15. package/dist/exceptions.js +13 -0
  16. package/dist/exceptions.js.map +1 -0
  17. package/dist/gate.d.ts +16 -0
  18. package/dist/gate.d.ts.map +1 -0
  19. package/dist/gate.js +66 -0
  20. package/dist/gate.js.map +1 -0
  21. package/dist/gate.test.d.ts +2 -0
  22. package/dist/gate.test.d.ts.map +1 -0
  23. package/dist/gate.test.js +33 -0
  24. package/dist/gate.test.js.map +1 -0
  25. package/dist/hasher.d.ts +5 -0
  26. package/dist/hasher.d.ts.map +1 -0
  27. package/dist/hasher.js +28 -0
  28. package/dist/hasher.js.map +1 -0
  29. package/dist/index.d.ts +18 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +15 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/oauth.d.ts +40 -0
  34. package/dist/oauth.d.ts.map +1 -0
  35. package/dist/oauth.js +178 -0
  36. package/dist/oauth.js.map +1 -0
  37. package/dist/oauth.test.d.ts +2 -0
  38. package/dist/oauth.test.d.ts.map +1 -0
  39. package/dist/oauth.test.js +17 -0
  40. package/dist/oauth.test.js.map +1 -0
  41. package/dist/password-reset-broker.d.ts +20 -0
  42. package/dist/password-reset-broker.d.ts.map +1 -0
  43. package/dist/password-reset-broker.js +68 -0
  44. package/dist/password-reset-broker.js.map +1 -0
  45. package/dist/personal-access-token-repository.d.ts +14 -0
  46. package/dist/personal-access-token-repository.d.ts.map +1 -0
  47. package/dist/personal-access-token-repository.js +55 -0
  48. package/dist/personal-access-token-repository.js.map +1 -0
  49. package/dist/policy.d.ts +3 -0
  50. package/dist/policy.d.ts.map +1 -0
  51. package/dist/policy.js +3 -0
  52. package/dist/policy.js.map +1 -0
  53. package/dist/session-guard.d.ts +26 -0
  54. package/dist/session-guard.d.ts.map +1 -0
  55. package/dist/session-guard.js +118 -0
  56. package/dist/session-guard.js.map +1 -0
  57. package/dist/session-store.d.ts +18 -0
  58. package/dist/session-store.d.ts.map +1 -0
  59. package/dist/session-store.js +69 -0
  60. package/dist/session-store.js.map +1 -0
  61. package/dist/session.d.ts +18 -0
  62. package/dist/session.d.ts.map +1 -0
  63. package/dist/session.js +33 -0
  64. package/dist/session.js.map +1 -0
  65. package/dist/token-guard.d.ts +20 -0
  66. package/dist/token-guard.d.ts.map +1 -0
  67. package/dist/token-guard.js +51 -0
  68. package/dist/token-guard.js.map +1 -0
  69. package/dist/token.test.d.ts +2 -0
  70. package/dist/token.test.d.ts.map +1 -0
  71. package/dist/token.test.js +11 -0
  72. package/dist/token.test.js.map +1 -0
  73. package/dist/types.d.ts +73 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +2 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/user-provider.d.ts +15 -0
  78. package/dist/user-provider.d.ts.map +1 -0
  79. package/dist/user-provider.js +30 -0
  80. package/dist/user-provider.js.map +1 -0
  81. package/package.json +41 -0
package/dist/oauth.js ADDED
@@ -0,0 +1,178 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { QueryBuilder } from '@tyravel/database';
3
+ export class GithubOAuthDriver {
4
+ config;
5
+ name = 'github';
6
+ constructor(config) {
7
+ this.config = config;
8
+ }
9
+ authorizationUrl(state) {
10
+ const params = new URLSearchParams({
11
+ client_id: this.config.clientId,
12
+ redirect_uri: this.config.redirectUri,
13
+ scope: (this.config.scopes ?? ['user:email']).join(' '),
14
+ state,
15
+ });
16
+ return `https://github.com/login/oauth/authorize?${params}`;
17
+ }
18
+ async exchangeCode(code) {
19
+ const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
20
+ method: 'POST',
21
+ headers: {
22
+ accept: 'application/json',
23
+ 'content-type': 'application/json',
24
+ },
25
+ body: JSON.stringify({
26
+ client_id: this.config.clientId,
27
+ client_secret: this.config.clientSecret,
28
+ code,
29
+ redirect_uri: this.config.redirectUri,
30
+ }),
31
+ });
32
+ const tokenJson = (await tokenRes.json());
33
+ if (!tokenJson.access_token) {
34
+ throw new Error(tokenJson.error ?? 'OAuth token exchange failed');
35
+ }
36
+ const userRes = await fetch('https://api.github.com/user', {
37
+ headers: {
38
+ authorization: `Bearer ${tokenJson.access_token}`,
39
+ accept: 'application/json',
40
+ 'user-agent': 'tyravel-auth',
41
+ },
42
+ });
43
+ const user = (await userRes.json());
44
+ return {
45
+ id: String(user.id),
46
+ email: user.email ?? null,
47
+ name: user.name ?? user.login ?? null,
48
+ avatar: user.avatar_url ?? null,
49
+ };
50
+ }
51
+ }
52
+ export class GoogleOAuthDriver {
53
+ config;
54
+ name = 'google';
55
+ constructor(config) {
56
+ this.config = config;
57
+ }
58
+ authorizationUrl(state) {
59
+ const params = new URLSearchParams({
60
+ client_id: this.config.clientId,
61
+ redirect_uri: this.config.redirectUri,
62
+ response_type: 'code',
63
+ scope: (this.config.scopes ?? ['openid', 'email', 'profile']).join(' '),
64
+ state,
65
+ access_type: 'online',
66
+ });
67
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
68
+ }
69
+ async exchangeCode(code) {
70
+ const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
71
+ method: 'POST',
72
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
73
+ body: new URLSearchParams({
74
+ client_id: this.config.clientId,
75
+ client_secret: this.config.clientSecret,
76
+ code,
77
+ grant_type: 'authorization_code',
78
+ redirect_uri: this.config.redirectUri,
79
+ }),
80
+ });
81
+ const tokenJson = (await tokenRes.json());
82
+ if (!tokenJson.access_token) {
83
+ throw new Error(tokenJson.error ?? 'OAuth token exchange failed');
84
+ }
85
+ const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
86
+ headers: { authorization: `Bearer ${tokenJson.access_token}` },
87
+ });
88
+ const user = (await userRes.json());
89
+ return {
90
+ id: user.id,
91
+ email: user.email ?? null,
92
+ name: user.name ?? null,
93
+ avatar: user.picture ?? null,
94
+ };
95
+ }
96
+ }
97
+ export class OAuthManager {
98
+ connection;
99
+ accountsTable;
100
+ userModel;
101
+ drivers = new Map();
102
+ constructor(providers, connection, accountsTable, userModel) {
103
+ this.connection = connection;
104
+ this.accountsTable = accountsTable;
105
+ this.userModel = userModel;
106
+ for (const [name, config] of Object.entries(providers)) {
107
+ if (name === 'github') {
108
+ this.drivers.set(name, new GithubOAuthDriver(config));
109
+ }
110
+ else if (name === 'google') {
111
+ this.drivers.set(name, new GoogleOAuthDriver(config));
112
+ }
113
+ }
114
+ }
115
+ createState() {
116
+ return randomBytes(24).toString('base64url');
117
+ }
118
+ redirectUrl(provider, state) {
119
+ const driver = this.drivers.get(provider);
120
+ if (!driver) {
121
+ throw new Error(`OAuth provider not configured: ${provider}`);
122
+ }
123
+ return driver.authorizationUrl(state);
124
+ }
125
+ async handleCallback(provider, code) {
126
+ const driver = this.drivers.get(provider);
127
+ if (!driver) {
128
+ throw new Error(`OAuth provider not configured: ${provider}`);
129
+ }
130
+ return driver.exchangeCode(code);
131
+ }
132
+ async findOrCreateUser(provider, profile) {
133
+ const existing = await new QueryBuilder(this.connection, this.accountsTable)
134
+ .where('provider', provider)
135
+ .where('provider_user_id', profile.id)
136
+ .first();
137
+ if (existing) {
138
+ const user = await this.userModel.find(existing.user_id);
139
+ if (user) {
140
+ return user;
141
+ }
142
+ }
143
+ const ModelClass = this.userModel;
144
+ let user = null;
145
+ if (profile.email) {
146
+ user = (await ModelClass.query()
147
+ .where('email', profile.email)
148
+ .firstModel());
149
+ }
150
+ if (!user) {
151
+ const now = Math.floor(Date.now() / 1000);
152
+ const inserted = await ModelClass.query().insert({
153
+ name: profile.name ?? 'User',
154
+ email: profile.email ?? `${provider}-${profile.id}@oauth.local`,
155
+ password: randomBytes(32).toString('hex'),
156
+ created_at: now,
157
+ updated_at: now,
158
+ });
159
+ user = (await ModelClass.find(inserted));
160
+ }
161
+ const linked = await new QueryBuilder(this.connection, this.accountsTable)
162
+ .where('provider', provider)
163
+ .where('provider_user_id', profile.id)
164
+ .first();
165
+ if (!linked) {
166
+ await new QueryBuilder(this.connection, this.accountsTable).insert({
167
+ user_id: Number(user.getAuthIdentifier()),
168
+ provider,
169
+ provider_user_id: profile.id,
170
+ email: profile.email,
171
+ avatar: profile.avatar,
172
+ created_at: Math.floor(Date.now() / 1000),
173
+ });
174
+ }
175
+ return user;
176
+ }
177
+ }
178
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.test.d.ts","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { GithubOAuthDriver } from './oauth.js';
3
+ describe('OAuth drivers', () => {
4
+ it('builds GitHub authorize URL with state', () => {
5
+ const driver = new GithubOAuthDriver({
6
+ clientId: 'cid',
7
+ clientSecret: 'secret',
8
+ redirectUri: 'http://localhost/callback',
9
+ scopes: ['user:email'],
10
+ });
11
+ const url = new URL(driver.authorizationUrl('state-123'));
12
+ expect(url.hostname).toBe('github.com');
13
+ expect(url.searchParams.get('client_id')).toBe('cid');
14
+ expect(url.searchParams.get('state')).toBe('state-123');
15
+ });
16
+ });
17
+ //# sourceMappingURL=oauth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.test.js","sourceRoot":"","sources":["../src/oauth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC;YACnC,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE,QAAQ;YACtB,WAAW,EAAE,2BAA2B;YACxC,MAAM,EAAE,CAAC,YAAY,CAAC;SACvB,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { DatabaseConnection } from '@tyravel/database';
2
+ import type { PasswordBrokerConfig } from './types.js';
3
+ import type { UserProvider } from './user-provider.js';
4
+ export declare class PasswordResetBroker {
5
+ private readonly connection;
6
+ private readonly config;
7
+ private readonly provider;
8
+ private readonly hasher;
9
+ constructor(connection: DatabaseConnection, config: PasswordBrokerConfig, provider: UserProvider);
10
+ sendResetLink(email: string): Promise<string>;
11
+ reset(input: {
12
+ email: string;
13
+ token: string;
14
+ password: string;
15
+ }): Promise<void>;
16
+ private tokenValid;
17
+ private hashToken;
18
+ private issueDummyToken;
19
+ }
20
+ //# sourceMappingURL=password-reset-broker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-reset-broker.d.ts","sourceRoot":"","sources":["../src/password-reset-broker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AASvD,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAGpB,UAAU,EAAE,kBAAkB,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,YAAY;IAGnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuB7C,KAAK,CAAC,KAAK,EAAE;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;CAGxB"}
@@ -0,0 +1,68 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { QueryBuilder } from '@tyravel/database';
3
+ import { Hasher } from './hasher.js';
4
+ import { InvalidResetTokenException } from './authorization-exceptions.js';
5
+ export class PasswordResetBroker {
6
+ connection;
7
+ config;
8
+ provider;
9
+ hasher = new Hasher();
10
+ constructor(connection, config, provider) {
11
+ this.connection = connection;
12
+ this.config = config;
13
+ this.provider = provider;
14
+ }
15
+ async sendResetLink(email) {
16
+ const user = await this.provider.retrieveByCredentials({ email });
17
+ if (!user) {
18
+ return this.issueDummyToken();
19
+ }
20
+ const plainToken = randomBytes(32).toString('hex');
21
+ const hashed = this.hashToken(plainToken);
22
+ const now = Math.floor(Date.now() / 1000);
23
+ await new QueryBuilder(this.connection, this.config.table)
24
+ .where('email', email)
25
+ .delete();
26
+ await new QueryBuilder(this.connection, this.config.table).insert({
27
+ email,
28
+ token: hashed,
29
+ created_at: now,
30
+ });
31
+ return plainToken;
32
+ }
33
+ async reset(input) {
34
+ const row = await new QueryBuilder(this.connection, this.config.table)
35
+ .where('email', input.email)
36
+ .first();
37
+ if (!row || !this.tokenValid(row, input.token)) {
38
+ throw new InvalidResetTokenException();
39
+ }
40
+ const user = await this.provider.retrieveByCredentials({ email: input.email });
41
+ if (!user) {
42
+ throw new InvalidResetTokenException();
43
+ }
44
+ const { Model } = await import('@tyravel/database');
45
+ const ModelClass = user.constructor;
46
+ const passwordHash = this.hasher.make(input.password);
47
+ await ModelClass.query()
48
+ .where('id', user.getAuthIdentifier())
49
+ .update({ password: passwordHash });
50
+ await new QueryBuilder(this.connection, this.config.table)
51
+ .where('email', input.email)
52
+ .delete();
53
+ }
54
+ tokenValid(row, plain) {
55
+ const ageMinutes = (Math.floor(Date.now() / 1000) - row.created_at) / 60;
56
+ if (ageMinutes > this.config.expireMinutes) {
57
+ return false;
58
+ }
59
+ return this.hashToken(plain) === row.token;
60
+ }
61
+ hashToken(token) {
62
+ return createHash('sha256').update(token).digest('hex');
63
+ }
64
+ issueDummyToken() {
65
+ return randomBytes(32).toString('hex');
66
+ }
67
+ }
68
+ //# sourceMappingURL=password-reset-broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-reset-broker.js","sourceRoot":"","sources":["../src/password-reset-broker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { DatabaseConnection } from '@tyravel/database';
2
+ import type { Authenticatable } from './types.js';
3
+ import type { NewAccessToken } from './types.js';
4
+ export declare class PersonalAccessTokenRepository {
5
+ private readonly connection;
6
+ private readonly table;
7
+ private readonly tokenableType;
8
+ constructor(connection: DatabaseConnection, table: string, tokenableType?: string);
9
+ createToken(user: Authenticatable, name: string, abilities?: string[], expiresAt?: Date): Promise<NewAccessToken>;
10
+ findUserIdByBearerToken(bearer: string, lookupUser: (id: number) => Promise<Authenticatable | null>): Promise<Authenticatable | null>;
11
+ revokeTokensForUser(userId: number): Promise<void>;
12
+ private hashToken;
13
+ }
14
+ //# sourceMappingURL=personal-access-token-repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personal-access-token-repository.d.ts","sourceRoot":"","sources":["../src/personal-access-token-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAcjD,qBAAa,6BAA6B;IAEtC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAFb,UAAU,EAAE,kBAAkB,EAC9B,KAAK,EAAE,MAAM,EACb,aAAa,SAAU;IAGpC,WAAW,CACf,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAM,EAAU,EAC3B,SAAS,CAAC,EAAE,IAAI,GACf,OAAO,CAAC,cAAc,CAAC;IAmBpB,uBAAuB,CAC3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAC1D,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAsB5B,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxD,OAAO,CAAC,SAAS;CAGlB"}
@@ -0,0 +1,55 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { QueryBuilder } from '@tyravel/database';
3
+ export class PersonalAccessTokenRepository {
4
+ connection;
5
+ table;
6
+ tokenableType;
7
+ constructor(connection, table, tokenableType = 'users') {
8
+ this.connection = connection;
9
+ this.table = table;
10
+ this.tokenableType = tokenableType;
11
+ }
12
+ async createToken(user, name, abilities = ['*'], expiresAt) {
13
+ const plainTextToken = randomBytes(32).toString('hex');
14
+ const hashed = this.hashToken(plainTextToken);
15
+ const now = Math.floor(Date.now() / 1000);
16
+ await new QueryBuilder(this.connection, this.table).insert({
17
+ tokenable_type: this.tokenableType,
18
+ tokenable_id: Number(user.getAuthIdentifier()),
19
+ name,
20
+ token: hashed,
21
+ abilities: JSON.stringify(abilities),
22
+ last_used_at: null,
23
+ expires_at: expiresAt ? Math.floor(expiresAt.getTime() / 1000) : null,
24
+ created_at: now,
25
+ });
26
+ return { plainTextToken, name, abilities };
27
+ }
28
+ async findUserIdByBearerToken(bearer, lookupUser) {
29
+ const hashed = this.hashToken(bearer);
30
+ const row = await new QueryBuilder(this.connection, this.table)
31
+ .where('token', hashed)
32
+ .first();
33
+ if (!row) {
34
+ return null;
35
+ }
36
+ if (row.expires_at !== null && row.expires_at < Math.floor(Date.now() / 1000)) {
37
+ return null;
38
+ }
39
+ const now = Math.floor(Date.now() / 1000);
40
+ await new QueryBuilder(this.connection, this.table)
41
+ .where('id', row.id)
42
+ .update({ last_used_at: now });
43
+ return lookupUser(row.tokenable_id);
44
+ }
45
+ async revokeTokensForUser(userId) {
46
+ await new QueryBuilder(this.connection, this.table)
47
+ .where('tokenable_type', this.tokenableType)
48
+ .where('tokenable_id', userId)
49
+ .delete();
50
+ }
51
+ hashToken(token) {
52
+ return createHash('sha256').update(token).digest('hex');
53
+ }
54
+ }
55
+ //# sourceMappingURL=personal-access-token-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personal-access-token-repository.js","sourceRoot":"","sources":["../src/personal-access-token-repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare abstract class Policy {
2
+ }
3
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,8BAAsB,MAAM;CAAG"}
package/dist/policy.js ADDED
@@ -0,0 +1,3 @@
1
+ export class Policy {
2
+ }
3
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,MAAM,OAAgB,MAAM;CAAG"}
@@ -0,0 +1,26 @@
1
+ import type { TyravelRequest } from '@tyravel/http';
2
+ import type { SessionStore } from './session.js';
3
+ import type { UserProvider } from './user-provider.js';
4
+ import type { Authenticatable, AuthConfig, Guard } from './types.js';
5
+ type WebResponse = globalThis.Response;
6
+ export declare class SessionGuard implements Guard {
7
+ private readonly provider;
8
+ private readonly store;
9
+ private readonly sessionConfig;
10
+ readonly name: string;
11
+ private session?;
12
+ private currentUser;
13
+ private request?;
14
+ constructor(name: string, provider: UserProvider, store: SessionStore, sessionConfig: AuthConfig['session']);
15
+ setRequest(request: TyravelRequest): void;
16
+ startSession(): Promise<void>;
17
+ persistSession(response: WebResponse): Promise<WebResponse>;
18
+ user(): Authenticatable | null;
19
+ id(): string | number | null;
20
+ check(): boolean;
21
+ attempt(credentials: Record<string, string>): Promise<boolean>;
22
+ login(user: Authenticatable): Promise<void>;
23
+ logout(): Promise<void>;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=session-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-guard.d.ts","sourceRoot":"","sources":["../src/session-guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAErE,KAAK,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC;AAMvC,qBAAa,YAAa,YAAW,KAAK;IAQtC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAThC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,OAAO,CAAC,CAAiB;gBAG/B,IAAI,EAAE,MAAM,EACK,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,UAAU,CAAC,SAAS,CAAC;IAKvD,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAInC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB7B,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAmBjE,IAAI,IAAI,eAAe,GAAG,IAAI;IAI9B,EAAE,IAAI,MAAM,GAAG,MAAM,GAAG,IAAI;IAI5B,KAAK,IAAI,OAAO;IAIV,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAe9D,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3C,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAS9B"}
@@ -0,0 +1,118 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { InvalidCredentialsException } from './exceptions.js';
3
+ import { Session } from './session.js';
4
+ function sessionKey(guard) {
5
+ return `auth.${guard}.user_id`;
6
+ }
7
+ export class SessionGuard {
8
+ provider;
9
+ store;
10
+ sessionConfig;
11
+ name;
12
+ session;
13
+ currentUser = null;
14
+ request;
15
+ constructor(name, provider, store, sessionConfig) {
16
+ this.provider = provider;
17
+ this.store = store;
18
+ this.sessionConfig = sessionConfig;
19
+ this.name = name;
20
+ }
21
+ setRequest(request) {
22
+ this.request = request;
23
+ }
24
+ async startSession() {
25
+ if (!this.request) {
26
+ throw new Error('Request not set on guard');
27
+ }
28
+ const cookieName = this.sessionConfig.cookie;
29
+ const existingId = readCookie(this.request, cookieName);
30
+ const id = existingId ?? randomBytes(32).toString('base64url');
31
+ const data = await this.store.read(id);
32
+ this.session = new Session(id, data);
33
+ this.request.session = this.session;
34
+ const userId = this.session.get(sessionKey(this.name));
35
+ if (userId !== undefined) {
36
+ this.currentUser = await this.provider.retrieveById(userId);
37
+ this.request.user = this.currentUser;
38
+ }
39
+ else if (!this.request.user) {
40
+ this.request.user = null;
41
+ }
42
+ }
43
+ async persistSession(response) {
44
+ if (!this.session) {
45
+ return response;
46
+ }
47
+ if (this.session.isDirty()) {
48
+ await this.store.write(this.session.id, this.session.all(), this.sessionConfig.lifetimeMinutes);
49
+ this.session.markClean();
50
+ }
51
+ const cookieName = this.sessionConfig.cookie;
52
+ const maxAge = this.sessionConfig.lifetimeMinutes * 60;
53
+ return withCookie(response, cookieName, this.session.id, maxAge);
54
+ }
55
+ user() {
56
+ return this.currentUser;
57
+ }
58
+ id() {
59
+ return this.currentUser?.getAuthIdentifier() ?? null;
60
+ }
61
+ check() {
62
+ return this.currentUser !== null;
63
+ }
64
+ async attempt(credentials) {
65
+ const user = await this.provider.retrieveByCredentials(credentials);
66
+ if (!user) {
67
+ throw new InvalidCredentialsException();
68
+ }
69
+ const valid = await this.provider.validateCredentials(user, credentials);
70
+ if (!valid) {
71
+ throw new InvalidCredentialsException();
72
+ }
73
+ await this.login(user);
74
+ return true;
75
+ }
76
+ async login(user) {
77
+ this.currentUser = user;
78
+ if (!this.session) {
79
+ throw new Error('Session not started');
80
+ }
81
+ this.session.put(sessionKey(this.name), user.getAuthIdentifier());
82
+ if (this.request) {
83
+ this.request.user = user;
84
+ }
85
+ }
86
+ async logout() {
87
+ this.currentUser = null;
88
+ if (this.session) {
89
+ this.session.forget(sessionKey(this.name));
90
+ }
91
+ if (this.request) {
92
+ this.request.user = null;
93
+ }
94
+ }
95
+ }
96
+ function readCookie(request, name) {
97
+ const header = request.header('cookie');
98
+ if (!header) {
99
+ return undefined;
100
+ }
101
+ for (const part of header.split(';')) {
102
+ const [key, ...rest] = part.trim().split('=');
103
+ if (key === name) {
104
+ return decodeURIComponent(rest.join('='));
105
+ }
106
+ }
107
+ return undefined;
108
+ }
109
+ function withCookie(response, name, value, maxAge) {
110
+ const headers = new Headers(response.headers);
111
+ headers.append('set-cookie', `${name}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`);
112
+ return new globalThis.Response(response.body, {
113
+ status: response.status,
114
+ statusText: response.statusText,
115
+ headers,
116
+ });
117
+ }
118
+ //# sourceMappingURL=session-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-guard.js","sourceRoot":"","sources":["../src/session-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { DatabaseConnection } from '@tyravel/database';
2
+ import type { SessionStore } from './session.js';
3
+ export declare class DatabaseSessionStore implements SessionStore {
4
+ private readonly connection;
5
+ private readonly table;
6
+ constructor(connection: DatabaseConnection, table?: string);
7
+ read(id: string): Promise<Record<string, unknown>>;
8
+ write(id: string, data: Record<string, unknown>, lifetimeMinutes: number): Promise<void>;
9
+ destroy(id: string): Promise<void>;
10
+ pruneExpired(lifetimeMinutes: number): Promise<void>;
11
+ }
12
+ export declare class MemorySessionStore implements SessionStore {
13
+ private readonly sessions;
14
+ read(id: string): Promise<Record<string, unknown>>;
15
+ write(id: string, data: Record<string, unknown>, _lifetimeMinutes: number): Promise<void>;
16
+ destroy(id: string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AASjD,qBAAa,oBAAqB,YAAW,YAAY;IAErD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,UAAU,EAAE,kBAAkB,EAC9B,KAAK,SAAa;IAG/B,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAgBlD,KAAK,CACT,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC;IA2BV,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,YAAY,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAM3D;AAED,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8C;IAEjE,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAIlD,KAAK,CACT,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAIV,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}