-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathpassport.express.txt
196 lines (171 loc) · 12.3 KB
/
passport.express.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
┏━━━━━━━━━━━━━━┓
┃ PASSPORT ┃
┗━━━━━━━━━━━━━━┛
VERSION ==> #0.7.0
SUMMARY ==> #Authentication abstraction layer, built as Connect middleware.
#Essentially tries to authenticate and:
# - on success, sets REQ.user USER
# - on failure, RES.end() 401
#Can persist REQ.user accross requests on REQ.session.passport.user (using EXPRESS-SESSION)
┌───────────┐
│ SETUP │
└───────────┘
PASSPORT.initialize([POPTS]) #Must be called before PASSPORT.authenticate() but after PASSPORT.use|[de]serializeUser()
->MDWR #Sets:
# - REQ._passport, an underlying property used by it
# - REQ.login|logout|is[Un]authenticated()
PASSPORT.use #Register a STRATEGY.
(['STRATEGY'], STRATEGY) #It only defines an alias 'STRATEGY' -> STRATEGY
#Def 'STRATEGY': STRATEGY.name
PASSPORT.unuse('STRATEGY') #
PASSPORT.strategy('STRATEGY')
->STRATEGY #
┌──────────────────┐
│ AUTHENTICATE │
└──────────────────┘
PASSPORT.authenticate #Tries to authenticate REQ with STRATEGY.
('STRATEGY'[_ARR][, AOPTS] #The STRATEGY can call:
[, AFUNC(ERR, ...)])->MDWR # - this.pass(): calls NEXT(), i.e. skip authentication
# - this.success(USER, USERINFO): REQ.login(USER, AOPTS) then NEXT([ERROR])
# - this.fail(['CHALLENGE', ]STATUS_NUM):
# - try next STRATEGY
# - if none, calls RES.end() with:
# - status code STATUS_NUM (def: 401)
# - WWW-Authenticate 'CHALLENGE,...' [S] (if 401 and there are some 'CHALLENGE')
# - this.error(): NEXT(ERROR)
# - this.redirect('URL'[, STATUS_NUM]): RES.end() with:
# - status code STATUS_NUM (def: 302)
# - Location: URL [S], Content-Length: 0 [S]
#If AFUNC defined, called instead of REQ|RES|NEXT for:
# - this.success(): AFUNC(null, USER, USERINFO)
# - this.fail(): AFUNC(null, false, 'CHALLENGE'[_ARR], STATUS_NUM[_ARR])
# - this.error(): AFUNC(ERROR)
AOPTS.assignProperty #'PROP'. Calls REQ.PROP = USER instead of REQ.login() on this.success()
PASSPORT.authorize(...) #Like PASSPORT.authenticate(...) but with AOPTS.assignProperty 'account'
AOPTS.failWithError #BOOL (def: false). Calls NEXT(ERROR) instead of RES.end() on this.fail() with:
# - ERROR.name 'AuthenticationError'
# - ERROR.message HTTP.STATUS_CODES[STATUS_NUM]
# - ERROR.status STATUS_NUM
AOPTS.success|failureRedirect #'URL'. Calls RES.redirect('URL') instead on this.success|fail()
AOPTS.successReturnOrRedirect #Same but 'URL' can be overriden by REQ.session.returnTo
AOPTS.success|failureFlash #'TYPE' or { type, message }
#Calls REQ.flash('TYPE', 'MSG') (see CONNECT-FLASH) on this.success|fail()
#Can also use AOPTS.type|message (def: 'success|error' for 'type')
AOPTS.success|failureMessage #STR or true (same as 'MSG')
#Pushes to REQ.session.messages STR_ARR on this.success|fail()
STRATEGY #Instead of using library, can create own STRATEGY. It must:
# - inherit from base class Passport.Strategy
# - define STRATEGY.authenticate(REQ[, AOPTS|'MSG'])
# - which must call this.pass|error|redirect|success|fail()
┌──────────┐
│ USER │
└──────────┘
REQ.login(USER #Sets REQ.user = USER
[, OPTS][, FUNC([ERROR])]) #FUNC is only required (and useful) when using PASSPORT.session()
REQ.logout
([, OPTS][, FUNC([ERROR])]) #Sets REQ.user = null
REQ.is[Un]Authenticated()
->BOOL #Returns Boolean(REQ.user)
REQ.user #USER
#Can change property name with POPTS.userProperty
┌──────────────┐
│ AUTHINFO │
└──────────────┘
REQ.authInfo #SERIALIZED_USERINFO
#Set at same time as REQ.user (or REQ.PROP if AOPTS.assignProperty)
#Uses PASSPORT.transformAuthInfo()
#Only if AOPTS.authInfo true (def)
PASSPRT.transformAuthInfo(...)#Same as serializeUser() except:
# - for USERINFO
# - is optional
# - FUNC2() can be sync FUNC2(USERINFO)->SERIALIZED_USERINFO
┌─────────────┐
│ SESSION │
└─────────────┘
PASSPORT.session([AOPTS]) #Allow persisting USER accross requests using EXPRESS-SESSION
->MDWR #REQ.login():
# - REQ.session.regenerate()
# - uses PASSPORT.serializeUser()
# - REQ.user -> REQ.session.passport.user
# - REQ.session.save()
# - if error, sets REQ.user = null
#PASSPORT.session():
# - REQ.session.passport.user -> REQ.user
# - uses PASSPORT.deserializeUser()
#REQ.logout():
# - deletes REQ.session.passport.user
# - REQ.session.save()
# - REQ.session.regenerate()
#Can disable with REQ.login() OPTS.session false
AOPTS.keepSessionInfo #BOOL (def: false).
#On REQ.login|logout(), deep merge new session to previous one instead of overridding.
PASSPORT.serializeUser #Register a FUNC that translates USER into SERIALIZED_USER.
(FUNC([REQ, ]USER, #Can be called several times: the first one that does not 'pass' will be used.
FUNC2(ERROR|'pass', #FUNC can throw
SERIALIZED_USER))) #At least one must be defined and match.
PASSPORT.deserializeUser(...) #Inverse
┌────────────────┐
│ STRATEGIES │
└────────────────┘
new PASSPORT-ANONYMOUS() #STRATEGY that just call this.pass() (1.0.1)
new PASSPORT-LOCAL #STRATEGY (1.0.0) that checks a USERNAME and PASSWORD:
([OPTS, ]FUNC([REQ, ] # - 'USERNAME' = REQ.body|query[OPTS.usernameField] (def: "username")
'USERNAME', 'PASSWORD', # - 'PASSWORD' = REQ.body|query[OPTS.passwordField] (def: "password")
FUNC2(ERROR, USER, USERINFO)))# - OPTS.username|passwordField can be VARR
# - FUNC() must translate them into USER
#Fires:
# - this.success(USER, USERINFO)
# - this.fail({ message }, 400) if !USERNAME|PASSWORD
# - message is OPTS.badRequestMessage (def: "Missing credentials")
# - this.fail(USERINFO) if !USER
# - this.error(ERROR) if ERROR
#To use REQ, must OPTS.passReqToCallback true
new PASPORT-HTTP.BasicStrategy#STRATEGY for HTTP Basic Authentication.
([OBJ, ]FUNC([REQ, ] #Fires:
USERNAME_STR, PASSWORD_STR, # - this.fail('Basic realm="REALM"', 401) (first round of HTTP Authentication Basic) if:
FUNC2(ERROR, USER))) # - no Authorization: Basic [C]
# - username or password is empty
# - !USER
# REALM is OBJ.realm (def: "Users")
# - this.fail(400) if Authorization [C] header is malformed
# - this.success(USER) if USER
# - this.error(ERROR) if ERROR)
#To use REQ, must OBJ.passReqToCallback true
#STRATEGY.name is "basic"
new PASSPORT-HTTP. #STRATEGY for HTTP Digest Authentication.
DigestStrategy([OBJ,]FNC(USER,#FUNC() retrieves USER and PASSWORD, then checks HASH(PASSWORD) == Authorization: response [C]
FUNC3(ERROR, USER, PASSWORD)) #PASSWORD is either plaintext PASSWORD_STR or { ha1: MD5(USERNAME:REALM:PASSWORD) }
[,FUNC2(OBJ,FNC4(EROR,BOOL))])#(prefer to avoid storing plaintext passwords).
#Fires:
# - this.fail('Digest VAR="VAL" ...', 401) (first round of HTTP Authentication Digest) if:
# - no Authorization: Digest [C]
# - no Authorization: username
# - !USER
# - Wrong password
# Where VAR="VAL" (all optional but realm):
# - realm: OBJ.realm STR (def: "Users")
# - nonce: 32 random alnum (use Math.random())
# - algorithm: OBJ.algorithm STR (def: "MD5")
# - qop: OBJ.qop STR[_ARR] ("auth-int" not implemented)
# - domain: OBJ.domain STR[_ARR]
# - opaque: OBJ.opaque STR
# - this.fail(400) if:
# - Authorization [C] header is malformed
# - Authorization: uri != REQ.url
# - this.error(ERROR): if ERROR
# - this.success(USER): if USER, and Authorization: response [C] = HASH(PASSWORD)
#If FUNC2(OBJ, FUNC4(BOOL)):
# - verify nonces are ok, with OBJ members: nonce, cnonce, nc, opaque:
# - should check nonce is same as what was sent
# - should check if nc > 1, that it is incrementing and using the same cnonce
# - must fire FUNC4(true) if ok
#If SSL, can probably skip FUNC2() and only set OBJ.realm
#STRATEGY.name is "digest"
new PASSPORT-HTTP-BEARER #Same as PASSPORT-LOCAL but uses TOKEN instead of USERNAME+PASSWORD, where TOKEN is
([OBJ, ]FUNC([REQ, ]TOKEN, #Authorization: TOKEN [C] or access_token in REQ.query|body (if REQ.body exists, must be OBJ).
FUNC2(ERROR, USER, OBJ2)) #Also:
# - this.fail(400) if malformed
# - this.fail('Bearer VAR="VAL"') if authorization problem:
# - realm="OBJ.realm" (def: "Users")
# - scope="OBJ.scope" (optional)
# - error="invalid_token" error_description="OBJ2.message" (second-round only)