@auth/azure-tables-adapter 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.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2022-2023, Balázs Orbán
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ <p align="center">
2
+ <br/>
3
+ <a href="https://authjs.dev" target="_blank">
4
+ <img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
5
+ </a>
6
+ <a href="https://azure.microsoft.com/en-us/products/storage/tables" target="_blank">
7
+ <img height="64px" src="https://authjs.dev/img/adapters/azure-tables.svg"/>
8
+ </a>
9
+ <h3 align="center"><b>Azure Table Storage Adapter</b> - NextAuth.js / Auth.js</a></h3>
10
+ <p align="center" style="align: center;">
11
+ <a href="https://npm.im/@auth/azure-tables-adapter">
12
+ <img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
13
+ </a>
14
+ <a href="https://npm.im/@auth/azure-tables-adapter">
15
+ <img alt="npm" src="https://img.shields.io/npm/v/@auth/azure-tables-adapter?color=green&label=@auth/azure-tables-adapter&style=flat-square">
16
+ </a>
17
+ <a href="https://www.npmtrends.com/@auth/azure-tables-adapter">
18
+ <img src="https://img.shields.io/npm/dm/@auth/azure-tables-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
19
+ </a>
20
+ <a href="https://github.com/nextauthjs/next-auth/stargazers">
21
+ <img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
22
+ </a>
23
+ </p>
24
+ </p>
25
+
26
+ ---
package/index.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An official <a href="https://azure.microsoft.com/en-us/products/storage/tables">Azure Table Storage</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://azure.microsoft.com/en-us/products/storage/tables">
5
+ * <img style={{display: "block"}} src="/img/adapters/azure-tables.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Installation
10
+ *
11
+ * ```bash npm2yarn2pnpm
12
+ * npm install next-auth @auth/azure-tables-adapter
13
+ * ```
14
+ *
15
+ * @module @auth/azure-tables-adapter
16
+ */
17
+ import type { Adapter } from "@auth/core/adapters";
18
+ import { GetTableEntityResponse, TableClient, TableEntityResult } from "@azure/data-tables";
19
+ export declare const keys: {
20
+ user: string;
21
+ userByEmail: string;
22
+ account: string;
23
+ accountByUserId: string;
24
+ session: string;
25
+ sessionByUserId: string;
26
+ verificationToken: string;
27
+ };
28
+ export declare function withoutKeys<T>(entity: GetTableEntityResponse<TableEntityResult<T>>): T;
29
+ /**
30
+ *
31
+ * 1. Create a table for authentication data, `auth` in the example below.
32
+ *
33
+ * ```js title="auth.ts"
34
+ * import type { AuthConfig } from "next-auth"
35
+ * import { TableStorageAdapter } from "@next-auth/azure-tables-adapter"
36
+ * import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
37
+ *
38
+ * const credential = new AzureNamedKeyCredential(
39
+ * process.env.AZURE_ACCOUNT,
40
+ * process.env.AZURE_ACCESS_KEY
41
+ * )
42
+ * const authClient = new TableClient(
43
+ * process.env.AZURE_TABLES_ENDPOINT,
44
+ * "auth",
45
+ * credential
46
+ * )
47
+ *
48
+ * // For more information on each option (and a full list of options) go to
49
+ * // https://authjs.dev/reference/configuration/auth-options
50
+ * export default const authConfig = {
51
+ * // https://authjs.dev/reference/providers/oauth-builtin
52
+ * providers: [
53
+ * // ...
54
+ * ],
55
+ * adapter: TableStorageAdapter(authClient),
56
+ * // ...
57
+ * } satisfies AuthConfig
58
+ * ```
59
+ *
60
+ * Environment variable are as follows:
61
+ *
62
+ * ```
63
+ * AZURE_ACCOUNT=storageaccountname
64
+ * AZURE_ACCESS_KEY=longRandomKey
65
+ * AZURE_TABLES_ENDPOINT=https://$AZURE_ACCOUNT.table.core.windows.net
66
+ * ```
67
+ *
68
+ */
69
+ export declare const TableStorageAdapter: (client: TableClient) => Adapter;
70
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,OAAO,EAKR,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EAClB,MAAM,oBAAoB,CAAA;AAI3B,eAAO,MAAM,IAAI;;;;;;;;CAQhB,CAAA;AAED,wBAAgB,WAAW,CAAC,CAAC,EAC3B,MAAM,EAAE,sBAAsB,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GACnD,CAAC,CAUH;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,mBAAmB,WAAY,WAAW,KAAG,OA+MzD,CAAA"}
package/index.js ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An official <a href="https://azure.microsoft.com/en-us/products/storage/tables">Azure Table Storage</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://azure.microsoft.com/en-us/products/storage/tables">
5
+ * <img style={{display: "block"}} src="/img/adapters/azure-tables.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Installation
10
+ *
11
+ * ```bash npm2yarn2pnpm
12
+ * npm install next-auth @auth/azure-tables-adapter
13
+ * ```
14
+ *
15
+ * @module @auth/azure-tables-adapter
16
+ */
17
+ globalThis.crypto ?? (globalThis.crypto = require("node:crypto").webcrypto);
18
+ export const keys = {
19
+ user: "user",
20
+ userByEmail: "userByEmail",
21
+ account: "account",
22
+ accountByUserId: "accountByUserId",
23
+ session: "session",
24
+ sessionByUserId: "sessionByUserId",
25
+ verificationToken: "verificationToken",
26
+ };
27
+ export function withoutKeys(entity) {
28
+ delete entity.partitionKey;
29
+ delete entity.rowKey;
30
+ // @ts-expect-error
31
+ delete entity.etag;
32
+ delete entity.timestamp;
33
+ // @ts-expect-error
34
+ delete entity["odata.metadata"];
35
+ return entity;
36
+ }
37
+ /**
38
+ *
39
+ * 1. Create a table for authentication data, `auth` in the example below.
40
+ *
41
+ * ```js title="auth.ts"
42
+ * import type { AuthConfig } from "next-auth"
43
+ * import { TableStorageAdapter } from "@next-auth/azure-tables-adapter"
44
+ * import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
45
+ *
46
+ * const credential = new AzureNamedKeyCredential(
47
+ * process.env.AZURE_ACCOUNT,
48
+ * process.env.AZURE_ACCESS_KEY
49
+ * )
50
+ * const authClient = new TableClient(
51
+ * process.env.AZURE_TABLES_ENDPOINT,
52
+ * "auth",
53
+ * credential
54
+ * )
55
+ *
56
+ * // For more information on each option (and a full list of options) go to
57
+ * // https://authjs.dev/reference/configuration/auth-options
58
+ * export default const authConfig = {
59
+ * // https://authjs.dev/reference/providers/oauth-builtin
60
+ * providers: [
61
+ * // ...
62
+ * ],
63
+ * adapter: TableStorageAdapter(authClient),
64
+ * // ...
65
+ * } satisfies AuthConfig
66
+ * ```
67
+ *
68
+ * Environment variable are as follows:
69
+ *
70
+ * ```
71
+ * AZURE_ACCOUNT=storageaccountname
72
+ * AZURE_ACCESS_KEY=longRandomKey
73
+ * AZURE_TABLES_ENDPOINT=https://$AZURE_ACCOUNT.table.core.windows.net
74
+ * ```
75
+ *
76
+ */
77
+ export const TableStorageAdapter = (client) => {
78
+ return {
79
+ async createUser(user) {
80
+ const id = crypto.randomUUID();
81
+ const newUser = {
82
+ ...user,
83
+ id,
84
+ };
85
+ await Promise.all([
86
+ client.createEntity({
87
+ ...newUser,
88
+ partitionKey: keys.userByEmail,
89
+ rowKey: user.email,
90
+ }),
91
+ client.createEntity({
92
+ ...newUser,
93
+ partitionKey: keys.user,
94
+ rowKey: id,
95
+ }),
96
+ ]);
97
+ return newUser;
98
+ },
99
+ async getUser(id) {
100
+ try {
101
+ const user = await client.getEntity(keys.user, id);
102
+ return withoutKeys(user);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ },
108
+ async getUserByEmail(email) {
109
+ try {
110
+ const user = await client.getEntity(keys.userByEmail, email);
111
+ return withoutKeys(user);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ },
117
+ async getUserByAccount({ providerAccountId, provider }) {
118
+ try {
119
+ const rowKey = `${providerAccountId}_${provider}`;
120
+ const account = await client.getEntity(keys.account, rowKey);
121
+ const user = await client.getEntity(keys.user, account.userId);
122
+ return withoutKeys(user);
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ },
128
+ async updateUser(user) {
129
+ const _user = await client.getEntity(keys.user, user.id);
130
+ const updatedUser = {
131
+ ...user,
132
+ partitionKey: keys.user,
133
+ rowKey: _user.id,
134
+ };
135
+ await client.updateEntity(updatedUser, "Merge");
136
+ return { ..._user, ...updatedUser };
137
+ },
138
+ async deleteUser(userId) {
139
+ try {
140
+ const user = await client.getEntity(keys.user, userId);
141
+ const { sessionToken } = await client.getEntity(keys.sessionByUserId, userId);
142
+ const accounts = withoutKeys(await client.getEntity(keys.accountByUserId, userId));
143
+ const deleteAccounts = Object.keys(accounts).map((property) => client.deleteEntity(keys.account, `${accounts[property]}_${property}`));
144
+ await Promise.allSettled([
145
+ client.deleteEntity(keys.userByEmail, user.email),
146
+ client.deleteEntity(keys.user, userId),
147
+ client.deleteEntity(keys.session, sessionToken),
148
+ client.deleteEntity(keys.sessionByUserId, userId),
149
+ ...deleteAccounts,
150
+ client.deleteEntity(keys.accountByUserId, userId),
151
+ ]);
152
+ return withoutKeys(user);
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ },
158
+ async linkAccount(account) {
159
+ try {
160
+ await client.createEntity({
161
+ ...account,
162
+ partitionKey: keys.account,
163
+ rowKey: `${account.providerAccountId}_${account.provider}`,
164
+ });
165
+ await client.upsertEntity({
166
+ partitionKey: keys.accountByUserId,
167
+ rowKey: account.userId,
168
+ [account.provider]: account.providerAccountId,
169
+ });
170
+ return account;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ },
176
+ async unlinkAccount({ providerAccountId, provider }) {
177
+ const rowKey = `${providerAccountId}_${provider}`;
178
+ const account = await client.getEntity(keys.account, rowKey);
179
+ await client.deleteEntity(keys.account, rowKey);
180
+ await client.deleteEntity(keys.accountByUserId, account.userId);
181
+ },
182
+ async createSession(session) {
183
+ await client.createEntity({
184
+ ...session,
185
+ partitionKey: keys.session,
186
+ rowKey: session.sessionToken,
187
+ });
188
+ await client.upsertEntity({
189
+ partitionKey: keys.sessionByUserId,
190
+ rowKey: session.userId,
191
+ sessionToken: session.sessionToken,
192
+ });
193
+ return session;
194
+ },
195
+ async getSessionAndUser(sessionToken) {
196
+ try {
197
+ const session = await client.getEntity(keys.session, sessionToken);
198
+ if (session.expires.valueOf() < Date.now()) {
199
+ await client.deleteEntity(keys.session, sessionToken);
200
+ }
201
+ const user = await client.getEntity(keys.user, session.userId);
202
+ return {
203
+ session: withoutKeys(session),
204
+ user: withoutKeys(user),
205
+ };
206
+ }
207
+ catch {
208
+ return null;
209
+ }
210
+ },
211
+ async updateSession(session) {
212
+ const _session = await client.getEntity(keys.session, session.sessionToken);
213
+ const newSession = {
214
+ expires: session.expires ?? _session.expires,
215
+ };
216
+ await client.updateEntity({
217
+ ...newSession,
218
+ partitionKey: keys.session,
219
+ rowKey: session.sessionToken,
220
+ });
221
+ return { ...withoutKeys(_session), ...newSession };
222
+ },
223
+ async deleteSession(sessionToken) {
224
+ try {
225
+ const session = await client.getEntity(keys.session, sessionToken);
226
+ await Promise.allSettled([
227
+ client.deleteEntity(keys.session, sessionToken),
228
+ client.deleteEntity(keys.sessionByUserId, session.userId),
229
+ ]);
230
+ return withoutKeys(session);
231
+ }
232
+ catch {
233
+ return null;
234
+ }
235
+ },
236
+ async createVerificationToken(token) {
237
+ await client.createEntity({
238
+ ...token,
239
+ partitionKey: keys.verificationToken,
240
+ rowKey: token.token,
241
+ });
242
+ return token;
243
+ },
244
+ async useVerificationToken({ identifier, token }) {
245
+ try {
246
+ const tokenEntity = await client.getEntity(keys.verificationToken, token);
247
+ if (tokenEntity.identifier !== identifier) {
248
+ return null;
249
+ }
250
+ await client.deleteEntity(keys.verificationToken, token);
251
+ return withoutKeys(tokenEntity);
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ },
257
+ };
258
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@auth/azure-tables-adapter",
3
+ "version": "0.1.0",
4
+ "description": "Azure Tables Storage adapter for next-auth.",
5
+ "homepage": "https://authjs.dev",
6
+ "repository": "https://github.com/nextauthjs/next-auth",
7
+ "bugs": {
8
+ "url": "https://github.com/nextauthjs/next-auth/issues"
9
+ },
10
+ "author": "Nikita Dmitrijev <nikitadmitry@gmail.com>",
11
+ "contributors": [
12
+ "Thang Huu Vu <hi@thvu.dev>"
13
+ ],
14
+ "license": "ISC",
15
+ "keywords": [
16
+ "next-auth",
17
+ "next.js",
18
+ "oauth",
19
+ "azure-tables",
20
+ "adapter"
21
+ ],
22
+ "type": "module",
23
+ "types": "./index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./index.d.ts",
27
+ "import": "./index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "*.d.ts*",
32
+ "*.js",
33
+ "src"
34
+ ],
35
+ "private": false,
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@auth/core": "0.15.2"
41
+ },
42
+ "peerDependencies": {
43
+ "@azure/data-tables": "^13.2.1"
44
+ },
45
+ "devDependencies": {
46
+ "jest": "^27.4.3",
47
+ "@azure/data-tables": "^13.2.1",
48
+ "@auth/adapter-test": "0.0.0",
49
+ "@auth/tsconfig": "0.0.0"
50
+ },
51
+ "jest": {
52
+ "preset": "@auth/adapter-test/jest"
53
+ },
54
+ "scripts": {
55
+ "test": "./tests/test.sh",
56
+ "test:watch": "./tests/test.sh -w",
57
+ "build": "tsc"
58
+ }
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,303 @@
1
+ /**
2
+ * <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
3
+ * <p style={{fontWeight: "normal"}}>An official <a href="https://azure.microsoft.com/en-us/products/storage/tables">Azure Table Storage</a> adapter for Auth.js / NextAuth.js.</p>
4
+ * <a href="https://azure.microsoft.com/en-us/products/storage/tables">
5
+ * <img style={{display: "block"}} src="/img/adapters/azure-tables.svg" width="48" />
6
+ * </a>
7
+ * </div>
8
+ *
9
+ * ## Installation
10
+ *
11
+ * ```bash npm2yarn2pnpm
12
+ * npm install next-auth @auth/azure-tables-adapter
13
+ * ```
14
+ *
15
+ * @module @auth/azure-tables-adapter
16
+ */
17
+
18
+ import type {
19
+ Adapter,
20
+ AdapterUser,
21
+ AdapterAccount,
22
+ AdapterSession,
23
+ VerificationToken,
24
+ } from "@auth/core/adapters"
25
+ import {
26
+ GetTableEntityResponse,
27
+ TableClient,
28
+ TableEntityResult,
29
+ } from "@azure/data-tables"
30
+
31
+ globalThis.crypto ??= require("node:crypto").webcrypto
32
+
33
+ export const keys = {
34
+ user: "user",
35
+ userByEmail: "userByEmail",
36
+ account: "account",
37
+ accountByUserId: "accountByUserId",
38
+ session: "session",
39
+ sessionByUserId: "sessionByUserId",
40
+ verificationToken: "verificationToken",
41
+ }
42
+
43
+ export function withoutKeys<T>(
44
+ entity: GetTableEntityResponse<TableEntityResult<T>>
45
+ ): T {
46
+ delete entity.partitionKey
47
+ delete entity.rowKey
48
+ // @ts-expect-error
49
+ delete entity.etag
50
+ delete entity.timestamp
51
+ // @ts-expect-error
52
+ delete entity["odata.metadata"]
53
+
54
+ return entity
55
+ }
56
+ /**
57
+ *
58
+ * 1. Create a table for authentication data, `auth` in the example below.
59
+ *
60
+ * ```js title="auth.ts"
61
+ * import type { AuthConfig } from "next-auth"
62
+ * import { TableStorageAdapter } from "@next-auth/azure-tables-adapter"
63
+ * import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
64
+ *
65
+ * const credential = new AzureNamedKeyCredential(
66
+ * process.env.AZURE_ACCOUNT,
67
+ * process.env.AZURE_ACCESS_KEY
68
+ * )
69
+ * const authClient = new TableClient(
70
+ * process.env.AZURE_TABLES_ENDPOINT,
71
+ * "auth",
72
+ * credential
73
+ * )
74
+ *
75
+ * // For more information on each option (and a full list of options) go to
76
+ * // https://authjs.dev/reference/configuration/auth-options
77
+ * export default const authConfig = {
78
+ * // https://authjs.dev/reference/providers/oauth-builtin
79
+ * providers: [
80
+ * // ...
81
+ * ],
82
+ * adapter: TableStorageAdapter(authClient),
83
+ * // ...
84
+ * } satisfies AuthConfig
85
+ * ```
86
+ *
87
+ * Environment variable are as follows:
88
+ *
89
+ * ```
90
+ * AZURE_ACCOUNT=storageaccountname
91
+ * AZURE_ACCESS_KEY=longRandomKey
92
+ * AZURE_TABLES_ENDPOINT=https://$AZURE_ACCOUNT.table.core.windows.net
93
+ * ```
94
+ *
95
+ */
96
+ export const TableStorageAdapter = (client: TableClient): Adapter => {
97
+ return {
98
+ async createUser(user) {
99
+ const id = crypto.randomUUID()
100
+ const newUser = {
101
+ ...user,
102
+ id,
103
+ }
104
+ await Promise.all([
105
+ client.createEntity({
106
+ ...newUser,
107
+ partitionKey: keys.userByEmail,
108
+ rowKey: user.email,
109
+ }),
110
+ client.createEntity({
111
+ ...newUser,
112
+ partitionKey: keys.user,
113
+ rowKey: id,
114
+ }),
115
+ ])
116
+ return newUser
117
+ },
118
+ async getUser(id: string) {
119
+ try {
120
+ const user = await client.getEntity<AdapterUser>(keys.user, id)
121
+ return withoutKeys(user)
122
+ } catch {
123
+ return null
124
+ }
125
+ },
126
+ async getUserByEmail(email) {
127
+ try {
128
+ const user = await client.getEntity<AdapterUser>(
129
+ keys.userByEmail,
130
+ email
131
+ )
132
+ return withoutKeys(user)
133
+ } catch {
134
+ return null
135
+ }
136
+ },
137
+ async getUserByAccount({ providerAccountId, provider }) {
138
+ try {
139
+ const rowKey = `${providerAccountId}_${provider}`
140
+ const account = await client.getEntity<AdapterAccount>(
141
+ keys.account,
142
+ rowKey
143
+ )
144
+ const user = await client.getEntity<AdapterUser>(
145
+ keys.user,
146
+ account.userId
147
+ )
148
+ return withoutKeys(user)
149
+ } catch {
150
+ return null
151
+ }
152
+ },
153
+ async updateUser(user) {
154
+ const _user = await client.getEntity<AdapterUser>(keys.user, user.id)
155
+ const updatedUser = {
156
+ ...user,
157
+ partitionKey: keys.user,
158
+ rowKey: _user.id,
159
+ }
160
+ await client.updateEntity(updatedUser, "Merge")
161
+ return { ..._user, ...updatedUser }
162
+ },
163
+ async deleteUser(userId) {
164
+ try {
165
+ const user = await client.getEntity<AdapterUser>(keys.user, userId)
166
+ const { sessionToken } = await client.getEntity<AdapterSession>(
167
+ keys.sessionByUserId,
168
+ userId
169
+ )
170
+ const accounts = withoutKeys(
171
+ await client.getEntity<AdapterAccount>(keys.accountByUserId, userId)
172
+ )
173
+ const deleteAccounts = Object.keys(accounts).map((property) =>
174
+ client.deleteEntity(keys.account, `${accounts[property]}_${property}`)
175
+ )
176
+ await Promise.allSettled([
177
+ client.deleteEntity(keys.userByEmail, user.email),
178
+ client.deleteEntity(keys.user, userId),
179
+ client.deleteEntity(keys.session, sessionToken),
180
+ client.deleteEntity(keys.sessionByUserId, userId),
181
+ ...deleteAccounts,
182
+ client.deleteEntity(keys.accountByUserId, userId),
183
+ ])
184
+ return withoutKeys(user)
185
+ } catch {
186
+ return null
187
+ }
188
+ },
189
+ async linkAccount(account) {
190
+ try {
191
+ await client.createEntity({
192
+ ...account,
193
+ partitionKey: keys.account,
194
+ rowKey: `${account.providerAccountId}_${account.provider}`,
195
+ })
196
+ await client.upsertEntity({
197
+ partitionKey: keys.accountByUserId,
198
+ rowKey: account.userId,
199
+ [account.provider]: account.providerAccountId,
200
+ })
201
+ return account
202
+ } catch {
203
+ return null
204
+ }
205
+ },
206
+ async unlinkAccount({ providerAccountId, provider }) {
207
+ const rowKey = `${providerAccountId}_${provider}`
208
+ const account = await client.getEntity<AdapterAccount>(
209
+ keys.account,
210
+ rowKey
211
+ )
212
+ await client.deleteEntity(keys.account, rowKey)
213
+ await client.deleteEntity(keys.accountByUserId, account.userId)
214
+ },
215
+ async createSession(session) {
216
+ await client.createEntity({
217
+ ...session,
218
+ partitionKey: keys.session,
219
+ rowKey: session.sessionToken,
220
+ })
221
+ await client.upsertEntity({
222
+ partitionKey: keys.sessionByUserId,
223
+ rowKey: session.userId,
224
+ sessionToken: session.sessionToken,
225
+ })
226
+ return session
227
+ },
228
+ async getSessionAndUser(sessionToken) {
229
+ try {
230
+ const session = await client.getEntity<AdapterSession>(
231
+ keys.session,
232
+ sessionToken
233
+ )
234
+ if (session.expires.valueOf() < Date.now()) {
235
+ await client.deleteEntity(keys.session, sessionToken)
236
+ }
237
+ const user = await client.getEntity<AdapterUser>(
238
+ keys.user,
239
+ session.userId
240
+ )
241
+ return {
242
+ session: withoutKeys(session),
243
+ user: withoutKeys(user),
244
+ }
245
+ } catch {
246
+ return null
247
+ }
248
+ },
249
+ async updateSession(session) {
250
+ const _session = await client.getEntity<AdapterSession>(
251
+ keys.session,
252
+ session.sessionToken
253
+ )
254
+ const newSession = {
255
+ expires: session.expires ?? _session.expires,
256
+ }
257
+ await client.updateEntity({
258
+ ...newSession,
259
+ partitionKey: keys.session,
260
+ rowKey: session.sessionToken,
261
+ })
262
+ return { ...withoutKeys(_session), ...newSession }
263
+ },
264
+ async deleteSession(sessionToken) {
265
+ try {
266
+ const session = await client.getEntity<AdapterSession>(
267
+ keys.session,
268
+ sessionToken
269
+ )
270
+ await Promise.allSettled([
271
+ client.deleteEntity(keys.session, sessionToken),
272
+ client.deleteEntity(keys.sessionByUserId, session.userId),
273
+ ])
274
+ return withoutKeys(session)
275
+ } catch {
276
+ return null
277
+ }
278
+ },
279
+ async createVerificationToken(token) {
280
+ await client.createEntity({
281
+ ...token,
282
+ partitionKey: keys.verificationToken,
283
+ rowKey: token.token,
284
+ })
285
+ return token
286
+ },
287
+ async useVerificationToken({ identifier, token }) {
288
+ try {
289
+ const tokenEntity = await client.getEntity<VerificationToken>(
290
+ keys.verificationToken,
291
+ token
292
+ )
293
+ if (tokenEntity.identifier !== identifier) {
294
+ return null
295
+ }
296
+ await client.deleteEntity(keys.verificationToken, token)
297
+ return withoutKeys(tokenEntity)
298
+ } catch {
299
+ return null
300
+ }
301
+ },
302
+ }
303
+ }