Skip to content

Commit bbb8001

Browse files
committed
feat(*): Initial commit
1 parent 83eb13f commit bbb8001

17 files changed

+2080
-0
lines changed

.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_size = 2
6+
indent_style = space
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
coverage
2+
node_modules
3+
.DS_Store
4+
npm-debug.log
5+
.idea
6+
out
7+
.nyc_output
8+
package-lock.json
9+
app/database.sqlite

.npmignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
coverage
2+
node_modules
3+
.DS_Store
4+
npm-debug.log
5+
test
6+
.travis.yml
7+
.editorconfig
8+
benchmarks
9+
.idea
10+
bin
11+
out
12+
.nyc_output

.travis.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: node_js
2+
node_js:
3+
- node
4+
- 8.0.0
5+
sudo: false
6+
install:
7+
- npm install
8+
notifications:
9+
slack:
10+
secure: m91zkX2cLVDRDMBAUnR1d+hbZqtSHXLkuPencHadhJ3C3wm53Box8U25co/goAmjnW5HNJ1SMSIg+DojtgDhqTbReSh5gSbU0uU8YaF8smbvmUv3b2Q8PRCA7f6hQiea+a8+jAb7BOvwh66dV4Al/1DJ2b4tCjPuVuxQ96Wll7Pnj1S7yW/Hb8fQlr9wc+INXUZOe8erFin+508r5h1L4Xv0N5ZmNw+Gqvn2kPJD8f/YBPpx0AeZdDssTL0IOcol1+cDtDzMw5PAkGnqwamtxhnsw+i8OW4avFt1GrRNlz3eci5Cb3NQGjHxJf+JIALvBeSqkOEFJIFGqwAXMctJ9q8/7XyXk7jVFUg5+0Z74HIkBwdtLwi/BTyXMZAgsnDjndmR9HsuBP7OSTJF5/V7HCJZAaO9shEgS8DwR78owv9Fr5er5m9IMI+EgSH3qtb8iuuQaPtflbk+cPD3nmYbDqmPwkSCXcXRfq3IxdcV9hkiaAw52AIqqhnAXJWZfL6+Ct32i2mtSaov9FYtp/G0xb4tjrUAsDUd/AGmMJNEBVoHtP7mKjrVQ35cEtFwJr/8SmZxGvOaJXPaLs43dhXKa2tAGl11wF02d+Rz1HhbOoq9pJvJuqkLAVvRdBHUJrB4/hnTta5B0W5pe3mIgLw3AmOpk+s/H4hAP4Hp0gOWlPA=

README.md

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
![](http://res.cloudinary.com/adonisjs/image/upload/q_100/v1522328931/adonis-persona_qlb1ix.svg)
2+
3+
> Opinionated user management service for AdonisJs
4+
5+
Since AdonisJs is all about removing redundant code from your code base. This add-on is another attempt for same.
6+
7+
8+
## What is Persona?
9+
10+
Persona is a simple functional service to let you **create**, **verify** and **update** user profiles.
11+
12+
Persona is not for everyone, if your login system is too complex and rely on many factors, then Persona is not for you. **However, persona works great for majority of use cases**.
13+
14+
## What is does?
15+
1. It helps you in registering new users.
16+
2. Generate email verification token.
17+
3. Validate credentials on login.
18+
4. On email change, set the user account to `pending` state and re-generate the email verification token.
19+
5. Allow password change.
20+
6. Allow forget password.
21+
22+
## What is doesn't?
23+
24+
1. Do not generate any routes, controllers or views for you.
25+
2. Do not send emails. However emit events that you can catch and send emails.
26+
3. Doesn't set any sessions or generate JWT tokens
27+
28+
29+
## Setup
30+
Run the following command to grab the add-on from npm.
31+
32+
```bash
33+
adonis install @adonisjs/persona
34+
35+
# for yarn
36+
adonis install @adonisjs/persona --yarn
37+
```
38+
39+
And then register the provider inside the providers array.
40+
41+
```js
42+
const providers = [
43+
'@adonisjs/persona/providers/PersonaProvider'
44+
]
45+
```
46+
47+
And then you can access it as follows
48+
49+
```js
50+
const Persona = use('Persona')
51+
```
52+
53+
## Config
54+
55+
The config file is saved as `config/persona.js`.
56+
57+
| Key | Value | Description |
58+
|-----|--------|------------|
59+
| uids | ['email'] | An array of database columns, that will be used as `uids`. If your system allows, `username` and `emails` both, then simply add them to this array.
60+
| email | email | The field to be used as email. Everytime user changes the value of this field, their account will be set to `pending` state.
61+
| password | password | The field to be used as password.
62+
| model | App/Models/User | The user model to be used.
63+
| newAccountState | pending | What is the account state of the user, when they first signup.
64+
| verifiedAccountState | active | The account state of the user when they verify their email address
65+
| dateFormat | YYYY-MM-DD HH:mm:ss | Your database date format, required for finding if the token has been expired or not.
66+
| validationMessages | function | A function that returns an object of messages to be used for validation. It is same the validator custom messages.
67+
68+
## Constraints
69+
70+
There are some intentional constraints in place.
71+
72+
1. Only works with `Lucid` models.
73+
2. The `App/Models/User` must have a relationship setup with `App/Models/Token` and vice-versa.
74+
75+
```js
76+
class User extends Model {
77+
tokens () {
78+
return this.hasMany('App/Models/Token')
79+
}
80+
}
81+
82+
class Token extends Model {
83+
user () {
84+
return this.belongsTo('App/Models/User')
85+
}
86+
}
87+
```
88+
89+
3. User table must have a column called `account_status`.
90+
91+
## API
92+
93+
Let's go through the API of persona.
94+
95+
#### register(payload, [callback])
96+
97+
> The optional `callback` is invoked with the original payload, just before the user is saved to the database. So this is your chance to attach any other properties to the payload.
98+
99+
The register method takes the user input data and perform following actions on it.
100+
101+
1. Validate that all `uids` are unique.
102+
2. Email is unique and is a valid email address.
103+
3. Password is confirmed.
104+
4. Creates user account with the `account_status = pending`.
105+
5. Generate and save email verification token inside the `tokens` table.
106+
5. Emits `user::created` event. You can listen this event to send an email to the user.
107+
108+
```js
109+
const Persona = use('Persona')
110+
111+
async register ({ request, auth, response }) {
112+
const payload = request.only(['email', 'password', 'password_confirmation'])
113+
114+
const user = await Persona.register(payload)
115+
116+
// optional
117+
await auth.login(user)
118+
response.redirect('/dashboard')
119+
}
120+
```
121+
122+
#### verify(payload, [callback])
123+
124+
> The optional `callback` is invoked with the user instance, just before the password verification. So this is your chance to check for `userRole` or any other property you want.
125+
126+
Verify the user credentials. The value of `uid` will be checked against all the `uids`.
127+
128+
```js
129+
async login ({ request, auth, response }) {
130+
const payload = request.only(['uid', 'password'])
131+
const user = await Persona.verify(payload)
132+
133+
await auth.login(user)
134+
response.redirect('/dashboard')
135+
})
136+
```
137+
138+
#### verifyEmail(token)
139+
140+
Verify user email using the token. Ideally it will be after someone clicks a URL from their email address.
141+
142+
1. It will remove the token from the tokens table.
143+
2. Set user `account_status = active`.
144+
145+
```js
146+
async verifyEmail ({ params, session, response }) {
147+
const user = await Persona.verifyEmail(params.token)
148+
149+
session.flash({ message: 'Email verified' })
150+
response.redirect('back')
151+
})
152+
```
153+
154+
#### updateProfile(user, payload)
155+
156+
Updates the user columns inside the database. However, if email is changed, then it will perform following steps.
157+
158+
> Note this method will throw exception if user is trying to change the password.
159+
160+
1. Set user `account_status = pending`.
161+
2. Generate email verification token.
162+
3. Fire `email::changed` event.
163+
164+
```js
165+
async update ({ request, auth }) {
166+
const payload = request.only(['firstname', 'email'])
167+
const user = auth.user
168+
await Persona.updateProfile(user, payload)
169+
})
170+
```
171+
172+
#### updatePassword(user, payload)
173+
174+
Updates the user password by performing following steps.
175+
176+
1. Ensure `old_password` matches the user password.
177+
2. New password is confirmed.
178+
3. Updates the user password
179+
4. Fires `password::changed` event. You can use this event to send an email about password change.
180+
181+
```js
182+
async updatePassword ({ request, auth }) {
183+
const payload = request.only(['old_password', 'password', 'password_confirmation'])
184+
const user = auth.user
185+
await Persona.updatePassword(user, payload)
186+
})
187+
```
188+
189+
#### forgotPassword(uid)
190+
191+
Take a forgot password request from the user by passing their `uid`. Uid will be matched for all the `uids` inside the config file.
192+
193+
1. Find a user with the matching uid.
194+
2. Generate password change token.
195+
3. Emit `forgot::password` event. You can use this event to send the email with the token to reset the password.
196+
197+
```js
198+
forgotPassword ({ request }) {
199+
await Persona.forgotPassword(request.input('uid'))
200+
}
201+
```
202+
203+
#### updatePasswordByToken(token, payload)
204+
205+
Update the user password by using a token. This method will perform following checks.
206+
207+
1. Make sure token is valid and not expired.
208+
2. Ensure password is confirmed.
209+
3. Update user password.
210+
211+
```js
212+
updatePasswordByToken ({ request, params }) {
213+
const token = params.token
214+
const payload = request.only(['password', 'password_confirmation'])
215+
216+
const user = await Persona.updatePasswordByToken(payload)
217+
}
218+
```
219+
220+
## Custom messages
221+
You can define a function inside `config/persona.js` file, which returns an object of messages to be used as validation messages. The syntax is same as the `Validator` custom messages.
222+
223+
```js
224+
{
225+
validationMessages (action) => {
226+
return {
227+
'email.required': 'Email is required',
228+
'password.mis_match': 'Invalid password'
229+
}
230+
}
231+
}
232+
```
233+
234+
The `validationMessages` method gets an `action` parameter. You can use it to customize the messages for different actions. Following is the list of actions.
235+
236+
1. register
237+
2. login
238+
3. emailUpdate
239+
4. passwordUpdate
240+
241+
## Events emitted
242+
243+
Below is the list of events emitted at different occasion.
244+
245+
| Event | Payload | Description |
246+
|--------|--------|-------------|
247+
| user::created | `{ user, token }` | Emitted when a new user is created |
248+
| email::changed | `{ user, oldEmail, token }` | Emitted when user changes their email address
249+
| password::changed | `{ user }` | When user change their password by providing the old password |
250+
| forgot::password | `{ user, token }` | Emitted when user asks for a token to change their password.
251+
| password::recovered | `{ user }` | Emitted when user password is changed using the token |
252+
253+
## Exceptions raised
254+
255+
The entire API is driven by exceptions, which means you will hardly have to write `if/else` statements.
256+
257+
This is great, since Adonis allows managing response by catching exceptions globally.
258+
259+
#### ValidationException
260+
The validation exception is raised when validation fails. If you are already handling `Validator` exceptions, then you won't have to do anything special.
261+
262+
#### InvalidTokenException
263+
Raised when the token user is using to verify their email, or reset password is invalid.

app/.env

Whitespace-only changes.

app/config/app.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
logger: {
3+
transport: 'console',
4+
console: {
5+
driver: 'console'
6+
}
7+
}
8+
}

app/config/database.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const path = require('path')
2+
3+
module.exports = {
4+
connection: 'sqlite',
5+
6+
sqlite: {
7+
client: 'sqlite3',
8+
connection: {
9+
filename: path.join(__dirname, '../database.sqlite')
10+
}
11+
}
12+
}

appveyor.yml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
environment:
2+
matrix:
3+
- nodejs_version: 'Stable'
4+
- nodejs_version: '8'
5+
6+
init:
7+
git config --global core.autocrlf true
8+
9+
install:
10+
- ps: Install-Product node $env:nodejs_version
11+
- npm install
12+
13+
test_script:
14+
- node --version
15+
- npm --version
16+
- npm run test:win
17+
18+
build: off
19+
clone_depth: 1
20+
21+
matrix:
22+
fast_finish: true

0 commit comments

Comments
 (0)