adonia 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 (140) hide show
  1. package/README.md +64 -0
  2. package/build/adonis_peers.d.ts +14 -0
  3. package/build/adonis_peers.js +56 -0
  4. package/build/cli.d.ts +2 -0
  5. package/build/cli.js +111 -0
  6. package/build/commands/add.d.ts +18 -0
  7. package/build/commands/add.js +246 -0
  8. package/build/commands/check.d.ts +4 -0
  9. package/build/commands/check.js +159 -0
  10. package/build/commands/diff.d.ts +2 -0
  11. package/build/commands/diff.js +74 -0
  12. package/build/commands/init.d.ts +6 -0
  13. package/build/commands/init.js +124 -0
  14. package/build/commands/list.d.ts +2 -0
  15. package/build/commands/list.js +34 -0
  16. package/build/config_merge.d.ts +10 -0
  17. package/build/config_merge.js +71 -0
  18. package/build/constants_merge.d.ts +4 -0
  19. package/build/constants_merge.js +85 -0
  20. package/build/copy.d.ts +8 -0
  21. package/build/copy.js +33 -0
  22. package/build/host.d.ts +15 -0
  23. package/build/host.js +85 -0
  24. package/build/registry/index.d.ts +26 -0
  25. package/build/registry/index.js +116 -0
  26. package/build/registry/plan.d.ts +53 -0
  27. package/build/registry/plan.js +159 -0
  28. package/build/schema.d.ts +136 -0
  29. package/build/schema.js +58 -0
  30. package/build/types.d.ts +21 -0
  31. package/build/types.js +1 -0
  32. package/build/wire.d.ts +16 -0
  33. package/build/wire.js +162 -0
  34. package/package.json +51 -0
  35. package/registry/README.md +86 -0
  36. package/registry/account/contracts/avatar_storage.ts +7 -0
  37. package/registry/account/contracts/index.ts +7 -0
  38. package/registry/account/contracts/types.ts +23 -0
  39. package/registry/account/events.ts +14 -0
  40. package/registry/account/module.json +31 -0
  41. package/registry/account/options.ts +11 -0
  42. package/registry/account/service.ts +155 -0
  43. package/registry/account/stubs/adapters/account.ts +21 -0
  44. package/registry/account/stubs/adapters/drive_avatar_storage.ts +15 -0
  45. package/registry/account/stubs/controllers/account_controller.ts +190 -0
  46. package/registry/account/stubs/routes/account.ts +53 -0
  47. package/registry/account/stubs/validators/account.ts +37 -0
  48. package/registry/account/tests/account_service.spec.ts +132 -0
  49. package/registry/account/tests/fakes/memory_avatar_storage.ts +12 -0
  50. package/registry/api/envelope.ts +66 -0
  51. package/registry/api/exception.ts +38 -0
  52. package/registry/api/exception_handler.ts +54 -0
  53. package/registry/api/module.json +27 -0
  54. package/registry/api/provider.ts +129 -0
  55. package/registry/api/service.ts +88 -0
  56. package/registry/api/tests/emit_safe.spec.ts +107 -0
  57. package/registry/api/tests/respond.spec.ts +40 -0
  58. package/registry/auth/contracts/index.ts +18 -0
  59. package/registry/auth/contracts/password_reset_store.ts +21 -0
  60. package/registry/auth/contracts/session_manager.ts +19 -0
  61. package/registry/auth/contracts/types.ts +32 -0
  62. package/registry/auth/events.ts +21 -0
  63. package/registry/auth/module.json +45 -0
  64. package/registry/auth/options.ts +23 -0
  65. package/registry/auth/service.ts +150 -0
  66. package/registry/auth/stubs/adapters/auth.ts +21 -0
  67. package/registry/auth/stubs/adapters/lucid_access_token_session.ts +33 -0
  68. package/registry/auth/stubs/adapters/lucid_password_reset_store.ts +68 -0
  69. package/registry/auth/stubs/adapters/lucid_user_store.ts +86 -0
  70. package/registry/auth/stubs/adapters/map_user.ts +19 -0
  71. package/registry/auth/stubs/controllers/auth_controller.ts +126 -0
  72. package/registry/auth/stubs/migrations/add_pending_email_to_users_table.ts +17 -0
  73. package/registry/auth/stubs/migrations/create_password_resets_table.ts +27 -0
  74. package/registry/auth/stubs/migrations/create_users_table.ts +31 -0
  75. package/registry/auth/stubs/models/password_reset.ts +29 -0
  76. package/registry/auth/stubs/models/user.ts +99 -0
  77. package/registry/auth/stubs/providers/vine_provider.ts +43 -0
  78. package/registry/auth/stubs/routes/auth.ts +36 -0
  79. package/registry/auth/stubs/start/limiter.ts +17 -0
  80. package/registry/auth/stubs/validators/auth.ts +48 -0
  81. package/registry/auth/stubs/validators/rules/exists.ts +21 -0
  82. package/registry/auth/stubs/validators/rules/unique.ts +21 -0
  83. package/registry/auth/stubs/validators/rules/valid_password.ts +20 -0
  84. package/registry/auth/stubs/validators/rules/valid_token.ts +27 -0
  85. package/registry/auth/stubs/validators/vine.d.ts +10 -0
  86. package/registry/auth/tests/auth_service.spec.ts +145 -0
  87. package/registry/auth/tests/auth_update_password.spec.ts +99 -0
  88. package/registry/auth/tests/fakes/index.ts +3 -0
  89. package/registry/auth/tests/fakes/memory_password_reset_store.ts +71 -0
  90. package/registry/auth/tests/fakes/memory_session_manager.ts +23 -0
  91. package/registry/auth/tests/fakes/memory_user_store.ts +89 -0
  92. package/registry/constants/exceptions.ts +64 -0
  93. package/registry/constants/index.ts +12 -0
  94. package/registry/constants/responseCodes.ts +87 -0
  95. package/registry/contracts/index.ts +5 -0
  96. package/registry/contracts/user.ts +78 -0
  97. package/registry/creem/contracts/creem_client.ts +34 -0
  98. package/registry/creem/contracts/index.ts +8 -0
  99. package/registry/creem/contracts/types.ts +30 -0
  100. package/registry/creem/events.ts +9 -0
  101. package/registry/creem/module.json +28 -0
  102. package/registry/creem/service.ts +87 -0
  103. package/registry/creem/stubs/adapters/creem.ts +23 -0
  104. package/registry/creem/stubs/adapters/creem_io_client.ts +70 -0
  105. package/registry/creem/tests/fakes/fake_creem_client.ts +68 -0
  106. package/registry/notification/contracts/app_notification_store.ts +8 -0
  107. package/registry/notification/contracts/index.ts +9 -0
  108. package/registry/notification/contracts/mail_transport.ts +7 -0
  109. package/registry/notification/contracts/types.ts +32 -0
  110. package/registry/notification/module.json +23 -0
  111. package/registry/notification/options.ts +11 -0
  112. package/registry/notification/service.ts +103 -0
  113. package/registry/notification/stubs/adapters/adonis_mail_transport.ts +15 -0
  114. package/registry/notification/stubs/adapters/lucid_app_notification_store.ts +21 -0
  115. package/registry/notification/stubs/adapters/notification.ts +36 -0
  116. package/registry/notification/stubs/migrations/create_notifications_table.ts +23 -0
  117. package/registry/notification/stubs/models/app_notification.ts +45 -0
  118. package/registry/notification/tests/fakes/memory_app_notification_store.ts +19 -0
  119. package/registry/notification/tests/fakes/memory_mail_transport.ts +16 -0
  120. package/registry/notification/tests/notification_service.spec.ts +63 -0
  121. package/registry/registry.json +4 -0
  122. package/registry/subscription/contracts/index.ts +9 -0
  123. package/registry/subscription/contracts/subscription_store.ts +15 -0
  124. package/registry/subscription/contracts/types.ts +39 -0
  125. package/registry/subscription/events.ts +13 -0
  126. package/registry/subscription/module.json +31 -0
  127. package/registry/subscription/options.ts +14 -0
  128. package/registry/subscription/service.ts +140 -0
  129. package/registry/subscription/stubs/adapters/lucid_subscription_store.ts +69 -0
  130. package/registry/subscription/stubs/adapters/subscription.ts +21 -0
  131. package/registry/subscription/stubs/migrations/create_subscriptions_table.ts +26 -0
  132. package/registry/subscription/stubs/migrations/subscriptions_user_creem_unique.ts +19 -0
  133. package/registry/subscription/stubs/models/subscription.ts +45 -0
  134. package/registry/subscription/tests/billing_service.spec.ts +189 -0
  135. package/registry/subscription/tests/fakes/memory_subscription_store.ts +63 -0
  136. package/registry/types/api.ts +41 -0
  137. package/registry/types/constants.ts +7 -0
  138. package/registry/types/index.ts +6 -0
  139. package/registry/types/models.ts +32 -0
  140. package/schema/adonia.schema.json +39 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * AuthService
3
+ *
4
+ * Domain auth use-cases. Depends on shared UserStore + auth-only contracts.
5
+ * Returns DTOs / void; controllers build HTTP envelopes and call ctx.respond.
6
+ */
7
+ import encryption from '@adonisjs/core/services/encryption'
8
+ import ApiService from '#modules/api/service'
9
+ import ApiException from '#modules/api/exception'
10
+ import { exceptions } from '#constants/exceptions'
11
+ import type { RegisterUserInput, User, UserStore } from '#modules/contracts/index'
12
+ import type {
13
+ AuthPasswordReset,
14
+ LoginCredentials,
15
+ LoginResult,
16
+ PasswordResetStore,
17
+ SessionManager,
18
+ UpdatePasswordInput,
19
+ } from '#modules/auth/contracts/index'
20
+ import { defaultAuthOptions, type AuthOptions } from '#modules/auth/options'
21
+ import '#modules/auth/events'
22
+
23
+ export class AuthService extends ApiService {
24
+ namespace = 'auth'
25
+
26
+ constructor(
27
+ private users: UserStore,
28
+ private passwordResets: PasswordResetStore,
29
+ private options: AuthOptions = defaultAuthOptions
30
+ ) {
31
+ super()
32
+ }
33
+
34
+ private accountUnverifiedException() {
35
+ return ApiException.from(exceptions.ACCOUNT_UNVERIFIED)
36
+ }
37
+
38
+ private invalidTokenException() {
39
+ return ApiException.from(exceptions.INVALID_TOKEN)
40
+ }
41
+
42
+ private tokensMatch(incoming: string, stored: string): boolean {
43
+ const storedToken = encryption.decrypt(stored)
44
+ return encryption.decrypt(incoming) === storedToken && storedToken != null
45
+ }
46
+
47
+ private issueVerificationToken(user: User, email = user.email): User {
48
+ return {
49
+ ...user,
50
+ emailVerificationToken: encryption.encrypt(email, this.options.verificationTtl),
51
+ }
52
+ }
53
+
54
+ private markVerified(user: User): User {
55
+ return {
56
+ ...user,
57
+ emailVerifiedAt: new Date(),
58
+ emailVerificationToken: null,
59
+ }
60
+ }
61
+
62
+ public async registerUser(data: RegisterUserInput): Promise<User> {
63
+ let user = await this.users.createEmailUser(data)
64
+ user = await this.users.save(this.issueVerificationToken(user))
65
+ this.emitSafe('Auth:RegisterUser', user)
66
+ return user
67
+ }
68
+
69
+ public async createNewVerificationToken(user: User): Promise<User> {
70
+ const updated = await this.users.save(this.issueVerificationToken(user))
71
+ this.emitSafe('Auth:CreateNewVerificationToken', updated)
72
+ return updated
73
+ }
74
+
75
+ /**
76
+ * Authenticates and opens a session. Throws E_ACCOUNT_UNVERIFIED with no
77
+ * side effects when the account is unverified — hosts that want a resend
78
+ * call createNewVerificationToken behind their own rate limit.
79
+ */
80
+ public async loginByEmail(
81
+ data: LoginCredentials,
82
+ session: SessionManager
83
+ ): Promise<LoginResult> {
84
+ const user = await this.users.verifyCredentials(data.email, data.password)
85
+ if (user.emailVerifiedAt === null) {
86
+ throw this.accountUnverifiedException()
87
+ }
88
+ const sessionResult = await session.login(user)
89
+ this.emitSafe('Auth:Login', { user })
90
+ return { user, session: sessionResult }
91
+ }
92
+
93
+ public async activateUserAccount(token: string): Promise<User> {
94
+ const user = await this.users.findByVerificationToken(token)
95
+ if (!user) {
96
+ throw this.invalidTokenException()
97
+ }
98
+ if (!user.emailVerificationToken || !this.tokensMatch(token, user.emailVerificationToken)) {
99
+ throw this.invalidTokenException()
100
+ }
101
+ const verified = await this.users.save(this.markVerified(user))
102
+ this.emitSafe('Auth:ActivateUserAccount', verified)
103
+ return verified
104
+ }
105
+
106
+ public async requestPasswordReset(email: string): Promise<void> {
107
+ const user = await this.users.findByEmailOrFail(email)
108
+ const reset = await this.passwordResets.createForUser(
109
+ user.id,
110
+ this.options.passwordResetTtl
111
+ )
112
+ this.emitSafe('Auth:RequestPasswordReset', {
113
+ user,
114
+ token: reset.token,
115
+ })
116
+ }
117
+
118
+ public async validatePasswordResetToken(token: string): Promise<AuthPasswordReset> {
119
+ const passwordReset = await this.passwordResets.findByToken(token)
120
+ if (!passwordReset || passwordReset.isExpired) {
121
+ throw this.invalidTokenException()
122
+ }
123
+ return this.passwordResets.refreshToken(
124
+ passwordReset,
125
+ this.options.passwordResetValidateTtl
126
+ )
127
+ }
128
+
129
+ public async updatePassword(
130
+ data: UpdatePasswordInput,
131
+ session?: SessionManager
132
+ ): Promise<void> {
133
+ const passwordReset = await this.passwordResets.findByToken(data.token)
134
+ if (!passwordReset || passwordReset.isExpired) {
135
+ throw this.invalidTokenException()
136
+ }
137
+ const user = await this.passwordResets.findUserByReset(passwordReset)
138
+ if (session) {
139
+ await session.logout()
140
+ }
141
+ await this.users.updatePassword(user, data.password)
142
+ await this.passwordResets.deleteForUser(user.id)
143
+ this.emitSafe('Auth:ResetPassword', { user })
144
+ }
145
+
146
+ public async logout(session: SessionManager): Promise<void> {
147
+ await session.logout()
148
+ this.emitSafe('Auth:Logout', null)
149
+ }
150
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Host Lucid factory for auth. Copied to app/adapters/auth.ts.
3
+ * Import as `#adapters/auth`.
4
+ */
5
+ import { AuthService } from '#modules/auth/service'
6
+ import { LucidUserStore } from './lucid_user_store.ts'
7
+ import { LucidPasswordResetStore } from './lucid_password_reset_store.ts'
8
+ import { defaultAuthOptions, type AuthOptions } from '#modules/auth/options'
9
+
10
+ export { LucidUserStore } from './lucid_user_store.ts'
11
+ export { LucidPasswordResetStore } from './lucid_password_reset_store.ts'
12
+ export { LucidAccessTokenSession } from './lucid_access_token_session.ts'
13
+ export { toUser } from './map_user.ts'
14
+
15
+ export function createAuthService(options: AuthOptions = defaultAuthOptions) {
16
+ return new AuthService(
17
+ new LucidUserStore(),
18
+ new LucidPasswordResetStore(options.passwordResetTokenBytes),
19
+ options
20
+ )
21
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SessionManager using Adonis access tokens.
3
+ * Copied to app/adapters/lucid_access_token_session.ts.
4
+ *
5
+ * Controllers construct this per request and pass it into AuthService.
6
+ */
7
+ import type { HttpContext } from '@adonisjs/core/http'
8
+ import UserModel from '#models/user'
9
+ import type { User } from '#modules/contracts/index'
10
+ import type {
11
+ SessionManager,
12
+ SessionResult,
13
+ } from '#modules/auth/contracts/index'
14
+
15
+ export class LucidAccessTokenSession implements SessionManager {
16
+ constructor(private ctx: HttpContext) {}
17
+
18
+ async login(user: User): Promise<SessionResult> {
19
+ const model = await UserModel.findOrFail(user.id)
20
+ const token = await UserModel.accessTokens.create(model)
21
+ return {
22
+ token: token.value!.release(),
23
+ expiresAt: token.expiresAt ?? undefined,
24
+ }
25
+ }
26
+
27
+ async logout(): Promise<void> {
28
+ const user = this.ctx.auth.getUserOrFail()
29
+ if ('currentAccessToken' in user && user.currentAccessToken) {
30
+ await UserModel.accessTokens.delete(user, user.currentAccessToken.identifier)
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Lucid PasswordResetStore. Copied to app/adapters/lucid_password_reset_store.ts.
3
+ * Expects PasswordReset at #models/password_reset with userId, token, expiresAt,
4
+ * and a belongsTo user relation.
5
+ */
6
+ import { DateTime } from 'luxon'
7
+ import string from '@adonisjs/core/helpers/string'
8
+ import encryption from '@adonisjs/core/services/encryption'
9
+ import PasswordReset from '#models/password_reset'
10
+ import { toUser } from './map_user.ts'
11
+ import { defaultAuthOptions } from '#modules/auth/options'
12
+ import type { User } from '#modules/contracts/index'
13
+ import type {
14
+ AuthPasswordReset,
15
+ PasswordResetStore,
16
+ TokenDuration,
17
+ } from '#modules/auth/contracts/index'
18
+
19
+ export class LucidPasswordResetStore implements PasswordResetStore {
20
+ constructor(private tokenBytes = defaultAuthOptions.passwordResetTokenBytes) {}
21
+
22
+ private toAuthPasswordReset(row: PasswordReset): AuthPasswordReset {
23
+ return {
24
+ id: row.id,
25
+ userId: row.userId,
26
+ token: row.token,
27
+ isExpired: row.expiresAt.toMillis() < DateTime.now().toMillis(),
28
+ }
29
+ }
30
+
31
+ private applyToken(row: PasswordReset, duration: TokenDuration) {
32
+ row.token = encryption.encrypt(string.generateRandom(this.tokenBytes))
33
+ row.expiresAt = DateTime.now().plus(duration)
34
+ }
35
+
36
+ async createForUser(userId: number, duration: TokenDuration): Promise<AuthPasswordReset> {
37
+ await this.deleteForUser(userId)
38
+ const row = new PasswordReset()
39
+ row.userId = userId
40
+ this.applyToken(row, duration)
41
+ await row.save()
42
+ return this.toAuthPasswordReset(row)
43
+ }
44
+
45
+ async findByToken(token: string): Promise<AuthPasswordReset | null> {
46
+ const row = await PasswordReset.findBy('token', token)
47
+ return row ? this.toAuthPasswordReset(row) : null
48
+ }
49
+
50
+ async deleteForUser(userId: number): Promise<void> {
51
+ await PasswordReset.query().where('user_id', userId).delete()
52
+ }
53
+
54
+ async refreshToken(
55
+ reset: AuthPasswordReset,
56
+ duration: TokenDuration
57
+ ): Promise<AuthPasswordReset> {
58
+ const row = await PasswordReset.findOrFail(reset.id)
59
+ this.applyToken(row, duration)
60
+ await row.save()
61
+ return this.toAuthPasswordReset(row)
62
+ }
63
+
64
+ async findUserByReset(reset: AuthPasswordReset): Promise<User> {
65
+ const row = await PasswordReset.query().where('id', reset.id).preload('user').firstOrFail()
66
+ return toUser(row.user)
67
+ }
68
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Shared Lucid UserStore. Copied to app/adapters/lucid_user_store.ts.
3
+ * Import as `#adapters/lucid_user_store`. Auth and account both use this file.
4
+ *
5
+ * Expects a User model at #models/user with email, password, authMethod,
6
+ * firstName, lastName, avatarKey, emailVerifiedAt, and emailVerificationToken.
7
+ */
8
+ import { DateTime } from 'luxon'
9
+ import hash from '@adonisjs/core/services/hash'
10
+ import UserModel from '#models/user'
11
+ import { toUser } from './map_user.ts'
12
+ import type { RegisterUserInput, User, UserStore } from '#modules/contracts/index'
13
+
14
+ export class LucidUserStore implements UserStore {
15
+ async createEmailUser(data: RegisterUserInput): Promise<User> {
16
+ const user = await UserModel.create({
17
+ email: data.email,
18
+ password: data.password,
19
+ firstName: data.firstName ?? data.fullName?.split(' ')[0] ?? '',
20
+ lastName: data.lastName ?? data.fullName?.split(' ').slice(1).join(' ') ?? '',
21
+ authMethod: 'email',
22
+ })
23
+ return toUser(user)
24
+ }
25
+
26
+ async findOrFail(id: number): Promise<User> {
27
+ const user = await UserModel.findOrFail(id)
28
+ return toUser(user)
29
+ }
30
+
31
+ async findByEmail(email: string): Promise<User | null> {
32
+ const user = await UserModel.findBy('email', email)
33
+ return user ? toUser(user) : null
34
+ }
35
+
36
+ async findByEmailOrFail(email: string): Promise<User> {
37
+ const user = await UserModel.findByOrFail('email', email)
38
+ return toUser(user)
39
+ }
40
+
41
+ async findByVerificationToken(token: string): Promise<User | null> {
42
+ const user = await UserModel.findBy('emailVerificationToken', token)
43
+ return user ? toUser(user) : null
44
+ }
45
+
46
+ async verifyCredentials(email: string, password: string): Promise<User> {
47
+ const user = await UserModel.verifyCredentials(email, password)
48
+ return toUser(user)
49
+ }
50
+
51
+ async verifyPassword(userId: number, password: string): Promise<boolean> {
52
+ const user = await UserModel.findOrFail(userId)
53
+ if (!user.password) {
54
+ return false
55
+ }
56
+ return hash.verify(user.password, password)
57
+ }
58
+
59
+ async save(dto: User): Promise<User> {
60
+ const user = await UserModel.findOrFail(dto.id)
61
+ user.email = dto.email
62
+ user.pendingEmail = dto.pendingEmail
63
+ user.authMethod = dto.authMethod
64
+ user.firstName = dto.firstName
65
+ user.lastName = dto.lastName
66
+ user.avatarKey = dto.avatarKey
67
+ user.emailVerificationToken = dto.emailVerificationToken
68
+ user.emailVerifiedAt = dto.emailVerifiedAt
69
+ ? DateTime.fromJSDate(dto.emailVerifiedAt)
70
+ : null
71
+ await user.save()
72
+ return toUser(user)
73
+ }
74
+
75
+ async updatePassword(dto: User, password: string): Promise<User> {
76
+ const user = await UserModel.findOrFail(dto.id)
77
+ user.password = password
78
+ await user.save()
79
+ return toUser(user)
80
+ }
81
+
82
+ async delete(userId: number): Promise<void> {
83
+ const user = await UserModel.findOrFail(userId)
84
+ await user.delete()
85
+ }
86
+ }
@@ -0,0 +1,19 @@
1
+ import type UserModel from '#models/user'
2
+ import type { User } from '#modules/contracts/index'
3
+
4
+ /**
5
+ * Map a Lucid User row to the portable User DTO.
6
+ */
7
+ export function toUser(user: UserModel): User {
8
+ return {
9
+ id: user.id,
10
+ email: user.email,
11
+ pendingEmail: user.pendingEmail ?? null,
12
+ authMethod: user.authMethod,
13
+ firstName: user.firstName,
14
+ lastName: user.lastName,
15
+ avatarKey: user.avatarKey ?? null,
16
+ emailVerifiedAt: user.emailVerifiedAt ? user.emailVerifiedAt.toJSDate() : null,
17
+ emailVerificationToken: user.emailVerificationToken,
18
+ }
19
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Example auth controller. Copy into app/controllers and register routes.
3
+ * Not registered by the starter.
4
+ *
5
+ * Copy Vine provider (`providers/vine_provider.ts`) and register it in
6
+ * adonisrc.ts so unique/exists/validToken/validPassword macros load.
7
+ * Logout needs named auth middleware from start/kernel.ts.
8
+ */
9
+ import type { HttpContext } from '@adonisjs/core/http'
10
+ import {
11
+ createAuthService,
12
+ LucidAccessTokenSession,
13
+ LucidUserStore,
14
+ } from '#adapters/auth'
15
+ import { responseCodes } from '#constants/responseCodes'
16
+ import {
17
+ loginValidator,
18
+ registerValidator,
19
+ activateValidator,
20
+ requestPasswordResetValidator,
21
+ updatePasswordValidator,
22
+ validatePasswordResetValidator,
23
+ resendActivationValidator,
24
+ } from '../validators/auth.ts'
25
+
26
+ const auth = createAuthService()
27
+
28
+ export default class AuthController {
29
+ async register(ctx: HttpContext) {
30
+ const data = await ctx.request.validateUsing(registerValidator)
31
+ const user = await auth.registerUser(data)
32
+ return ctx.response.created(
33
+ await ctx.respond({
34
+ success: true,
35
+ message: responseCodes.AUTH_REGISTER.code,
36
+ data: user,
37
+ })
38
+ )
39
+ }
40
+
41
+ async login(ctx: HttpContext) {
42
+ const credentials = await ctx.request.validateUsing(loginValidator)
43
+ const session = new LucidAccessTokenSession(ctx)
44
+ const { user, session: sessionResult } = await auth.loginByEmail(credentials, session)
45
+ return ctx.response.ok(
46
+ await ctx.respond({
47
+ success: true,
48
+ message: responseCodes.AUTH_LOGIN.code,
49
+ data: { user, token: sessionResult.token ?? null },
50
+ })
51
+ )
52
+ }
53
+
54
+ async logout(ctx: HttpContext) {
55
+ const session = new LucidAccessTokenSession(ctx)
56
+ await auth.logout(session)
57
+ return ctx.response.ok(
58
+ await ctx.respond({
59
+ success: true,
60
+ message: responseCodes.AUTH_LOGOUT.code,
61
+ data: null,
62
+ })
63
+ )
64
+ }
65
+
66
+ async activate(ctx: HttpContext) {
67
+ const { token } = await ctx.request.validateUsing(activateValidator)
68
+ const user = await auth.activateUserAccount(token)
69
+ return ctx.response.ok(
70
+ await ctx.respond({
71
+ success: true,
72
+ message: responseCodes.AUTH_ACTIVATE_ACCOUNT.code,
73
+ data: user,
74
+ })
75
+ )
76
+ }
77
+
78
+ async sendAccountActivationEmail(ctx: HttpContext) {
79
+ const { email } = await ctx.request.validateUsing(resendActivationValidator)
80
+ const user = await new LucidUserStore().findByEmailOrFail(email)
81
+ await auth.createNewVerificationToken(user)
82
+ return ctx.response.ok(
83
+ await ctx.respond({
84
+ success: true,
85
+ message: responseCodes.AUTH_ACTIVATE_ACCOUNT_REQUEST.code,
86
+ data: null,
87
+ })
88
+ )
89
+ }
90
+
91
+ async requestPasswordReset(ctx: HttpContext) {
92
+ const { email } = await ctx.request.validateUsing(requestPasswordResetValidator)
93
+ await auth.requestPasswordReset(email)
94
+ return ctx.response.ok(
95
+ await ctx.respond({
96
+ success: true,
97
+ message: responseCodes.AUTH_RESET_PASSWORD_REQUEST.code,
98
+ data: null,
99
+ })
100
+ )
101
+ }
102
+
103
+ async validatePasswordReset(ctx: HttpContext) {
104
+ const { token } = await ctx.request.validateUsing(validatePasswordResetValidator)
105
+ const reset = await auth.validatePasswordResetToken(token)
106
+ return ctx.response.ok(
107
+ await ctx.respond({
108
+ success: true,
109
+ message: responseCodes.AUTH_VALIDATE_PASSWORD_RESET.code,
110
+ data: reset,
111
+ })
112
+ )
113
+ }
114
+
115
+ async updatePassword(ctx: HttpContext) {
116
+ const data = await ctx.request.validateUsing(updatePasswordValidator)
117
+ await auth.updatePassword(data)
118
+ return ctx.response.ok(
119
+ await ctx.respond({
120
+ success: true,
121
+ message: responseCodes.AUTH_UPDATE_PASSWORD.code,
122
+ data: null,
123
+ })
124
+ )
125
+ }
126
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseSchema } from '@adonisjs/lucid/schema'
2
+
3
+ export default class extends BaseSchema {
4
+ protected tableName = 'users'
5
+
6
+ async up() {
7
+ this.schema.alterTable(this.tableName, (table) => {
8
+ table.string('pending_email', 254).nullable()
9
+ })
10
+ }
11
+
12
+ async down() {
13
+ this.schema.alterTable(this.tableName, (table) => {
14
+ table.dropColumn('pending_email')
15
+ })
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ import { BaseSchema } from '@adonisjs/lucid/schema'
2
+
3
+ export default class extends BaseSchema {
4
+ protected tableName = 'password_resets'
5
+
6
+ async up() {
7
+ this.schema.createTable(this.tableName, (table) => {
8
+ table.increments('id').primary()
9
+ table
10
+ .integer('user_id')
11
+ .unsigned()
12
+ .unique()
13
+ .notNullable()
14
+ .references('id')
15
+ .inTable('users')
16
+ .onDelete('CASCADE')
17
+ table.string('token').notNullable().index()
18
+ table.timestamp('created_at')
19
+ table.timestamp('updated_at')
20
+ table.timestamp('expires_at', { useTz: true })
21
+ })
22
+ }
23
+
24
+ async down() {
25
+ this.schema.dropTable(this.tableName)
26
+ }
27
+ }
@@ -0,0 +1,31 @@
1
+ import { BaseSchema } from '@adonisjs/lucid/schema'
2
+
3
+ export default class extends BaseSchema {
4
+ protected tableName = 'users'
5
+
6
+ async up() {
7
+ this.schema.createTable(this.tableName, (table) => {
8
+ table.increments('id').notNullable()
9
+ table.string('auth_method').notNullable()
10
+ table.string('email', 254).notNullable().unique()
11
+ table.string('first_name').notNullable()
12
+ table.string('last_name').notNullable()
13
+ table.string('avatar_key').nullable()
14
+ table.string('password').nullable()
15
+ table.string('email_verification_token').nullable()
16
+ table.string('creem_customer_id').unique().nullable()
17
+ table.boolean('email_marketing').defaultTo(false)
18
+ table.boolean('email_product_updates').defaultTo(false)
19
+ table.boolean('email_security_alerts').defaultTo(true)
20
+ table.boolean('email_weekly_digest').defaultTo(false)
21
+
22
+ table.timestamp('created_at').notNullable()
23
+ table.timestamp('updated_at').nullable()
24
+ table.timestamp('email_verified_at').nullable()
25
+ })
26
+ }
27
+
28
+ async down() {
29
+ this.schema.dropTable(this.tableName)
30
+ }
31
+ }
@@ -0,0 +1,29 @@
1
+ import { DateTime } from 'luxon'
2
+ import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm'
3
+ import type { BelongsTo } from '@adonisjs/lucid/types/relations'
4
+ import User from '#models/user'
5
+
6
+ export default class PasswordReset extends BaseModel {
7
+ static table = 'password_resets'
8
+
9
+ @column({ isPrimary: true })
10
+ declare id: number
11
+
12
+ @column()
13
+ declare userId: number
14
+
15
+ @column()
16
+ declare token: string
17
+
18
+ @column.dateTime({ autoCreate: true })
19
+ declare createdAt: DateTime
20
+
21
+ @column.dateTime({ autoCreate: true, autoUpdate: true })
22
+ declare updatedAt: DateTime
23
+
24
+ @column.dateTime()
25
+ declare expiresAt: DateTime
26
+
27
+ @belongsTo(() => User)
28
+ declare user: BelongsTo<typeof User>
29
+ }