auth
auth/context.go
Move login concerns to session, add login handler. Move all our helpers for authenticating, building a login redirect, and reading a cookie to session.go. Rewrite our passphrase scheme code so that a scheme is just a struct with three functions for checking a passphrase against a profile object, generating a passphrase, and calculating the number of iterations to use when generating a passphrase. Define an implementation of our passphrase scheme (scheme #1) using PBKDF2 and SHA256. Add a CreateSessionHandler function that logs the user in using their login and passphrase. Add a RegisterSessionHandlers function that adds the session-related handlers (right now, just our CreateSessionHandler) to the specified router.
1 package auth
3 import (
4 "html/template"
5 "io"
6 "log"
7 "net/url"
8 "time"
10 "code.secondbit.org/uuid"
11 )
13 // Context wraps the different storage interfaces and should
14 // be used as the main point of interaction for the data storage
15 // layer.
16 type Context struct {
17 template *template.Template
18 loginURI *url.URL
19 clients clientStore
20 authCodes authorizationCodeStore
21 profiles profileStore
22 tokens tokenStore
23 sessions sessionStore
24 config Config
25 }
27 // NewContext takes a Config instance and uses it to bootstrap a Context
28 // using the information provided in the Config variable.
29 func NewContext(config Config) (Context, error) {
30 context := Context{
31 clients: config.ClientStore,
32 authCodes: config.AuthCodeStore,
33 profiles: config.ProfileStore,
34 tokens: config.TokenStore,
35 sessions: config.SessionStore,
36 template: config.Template,
37 config: config,
38 }
39 var err error
40 context.loginURI, err = url.Parse(config.LoginURI)
41 if err != nil {
42 log.Println(err)
43 return Context{}, ErrInvalidLoginURI
44 }
45 return context, nil
46 }
48 // Render uses the HTML templates associated with the Context to render the
49 // template specified by name to out using data to fill any template variables.
50 func (c Context) Render(out io.Writer, name string, data interface{}) {
51 if c.template == nil {
52 log.Println("No template set on Context, can't render anything!")
53 return
54 }
55 err := c.template.ExecuteTemplate(out, name, data)
56 if err != nil {
57 log.Println("Error executing template", name, ":", err)
58 }
59 }
61 // GetClient returns a single Client by its ID from the
62 // clientStore associated with the Context.
63 func (c Context) GetClient(id uuid.ID) (Client, error) {
64 if c.clients == nil {
65 return Client{}, ErrNoClientStore
66 }
67 return c.clients.getClient(id)
68 }
70 // SaveClient stores the passed Client in the clientStore
71 // associated with the Context.
72 func (c Context) SaveClient(client Client) error {
73 if c.clients == nil {
74 return ErrNoClientStore
75 }
76 return c.clients.saveClient(client)
77 }
79 // UpdateClient applies the specified ClientChange to the Client
80 // with the specified ID in the clientStore associated with the
81 // Context.
82 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
83 if c.clients == nil {
84 return ErrNoClientStore
85 }
86 return c.clients.updateClient(id, change)
87 }
89 // DeleteClient removes the client with the specified ID from the
90 // clientStore associated with the Context.
91 func (c Context) DeleteClient(id uuid.ID) error {
92 if c.clients == nil {
93 return ErrNoClientStore
94 }
95 return c.clients.deleteClient(id)
96 }
98 // ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
99 // that have the specified OwnerID in the clientStore associated with the Context.
100 func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
101 if c.clients == nil {
102 return []Client{}, ErrNoClientStore
103 }
104 return c.clients.listClientsByOwner(ownerID, num, offset)
105 }
107 // AddEndpoint stores the specified Endpoint in the clientStore associated with the Context,
108 // and associates the newly-stored Endpoint with the Client specified by the passed ID.
109 func (c Context) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
110 if c.clients == nil {
111 return ErrNoClientStore
112 }
113 return c.clients.addEndpoint(client, endpoint)
114 }
116 // RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
117 // with the Context, and disassociates the Endpoint from the specified Client.
118 func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
119 if c.clients == nil {
120 return ErrNoClientStore
121 }
122 return c.clients.removeEndpoint(client, endpoint)
123 }
125 // CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
126 // to the Client specified by the passed ID and match the URI passed. URI matches must be
127 // performed according to RFC 3986 Section 6.
128 func (c Context) CheckEndpoint(client uuid.ID, URI string) (bool, error) {
129 if c.clients == nil {
130 return false, ErrNoClientStore
131 }
132 return c.clients.checkEndpoint(client, URI)
133 }
135 // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
136 // to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
137 // exclusive.
138 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
139 if c.clients == nil {
140 return []Endpoint{}, ErrNoClientStore
141 }
142 return c.clients.listEndpoints(client, num, offset)
143 }
145 // CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
146 // passed ID in the clientStore associated with the Context.
147 func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
148 if c.clients == nil {
149 return 0, ErrNoClientStore
150 }
151 return c.clients.countEndpoints(client)
152 }
154 // GetAuthorizationCode returns the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with the
155 // Context.
156 func (c Context) GetAuthorizationCode(code string) (AuthorizationCode, error) {
157 if c.authCodes == nil {
158 return AuthorizationCode{}, ErrNoAuthorizationCodeStore
159 }
160 return c.authCodes.getAuthorizationCode(code)
161 }
163 // SaveAuthorizationCode stores the passed AuthorizationCode in the authorizationCodeStore associated with the Context.
164 func (c Context) SaveAuthorizationCode(authCode AuthorizationCode) error {
165 if c.authCodes == nil {
166 return ErrNoAuthorizationCodeStore
167 }
168 return c.authCodes.saveAuthorizationCode(authCode)
169 }
171 // DeleteAuthorizationCode removes the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with
172 // the Context.
173 func (c Context) DeleteAuthorizationCode(code string) error {
174 if c.authCodes == nil {
175 return ErrNoAuthorizationCodeStore
176 }
177 return c.authCodes.deleteAuthorizationCode(code)
178 }
180 // UseAuthorizationCode marks the AuthorizationCode specified by the provided code as used in the authorizationCodeStore associated with
181 // the Context. Once an AuthorizationCode is marked as used, its Used property will be set to true when retrieved from the authorizationCodeStore.
182 func (c Context) UseAuthorizationCode(code string) error {
183 if c.authCodes == nil {
184 return ErrNoAuthorizationCodeStore
185 }
186 return c.authCodes.useAuthorizationCode(code)
187 }
189 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
190 // the Context.
191 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
192 if c.profiles == nil {
193 return Profile{}, ErrNoProfileStore
194 }
195 return c.profiles.getProfileByID(id)
196 }
198 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
199 // with the Context.
200 func (c Context) GetProfileByLogin(value string) (Profile, error) {
201 if c.profiles == nil {
202 return Profile{}, ErrNoProfileStore
203 }
204 return c.profiles.getProfileByLogin(value)
205 }
207 // SaveProfile inserts the passed Profile into the profileStore associated with the Context.
208 func (c Context) SaveProfile(profile Profile) error {
209 if c.profiles == nil {
210 return ErrNoProfileStore
211 }
212 return c.profiles.saveProfile(profile)
213 }
215 // UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
216 // in the profileStore associated with the Context.
217 func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
218 if c.profiles == nil {
219 return ErrNoProfileStore
220 }
221 return c.profiles.updateProfile(id, change)
222 }
224 // UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
225 // specified IDs in the profileStore associated with the Context.
226 func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
227 if c.profiles == nil {
228 return ErrNoProfileStore
229 }
230 return c.profiles.updateProfiles(ids, change)
231 }
233 // DeleteProfile removes the Profile specified by the passed ID from the profileStore associated
234 // with the Context.
235 func (c Context) DeleteProfile(id uuid.ID) error {
236 if c.profiles == nil {
237 return ErrNoProfileStore
238 }
239 return c.profiles.deleteProfile(id)
240 }
242 // AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
243 // the newly-created Login with the Orofile in login.ProfileID.
244 func (c Context) AddLogin(login Login) error {
245 if c.profiles == nil {
246 return ErrNoProfileStore
247 }
248 return c.profiles.addLogin(login)
249 }
251 // RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
252 // the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
253 // deleted Login from the Profile in login.ProfileID.
254 func (c Context) RemoveLogin(value string, profile uuid.ID) error {
255 if c.profiles == nil {
256 return ErrNoProfileStore
257 }
258 return c.profiles.removeLogin(value, profile)
259 }
261 // RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
262 // the Context to the value passed in as when.
263 func (c Context) RecordLoginUse(value string, when time.Time) error {
264 if c.profiles == nil {
265 return ErrNoProfileStore
266 }
267 return c.profiles.recordLoginUse(value, when)
268 }
270 // ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
271 // associated with the Context, skipping offset Profiles.
272 func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
273 if c.profiles == nil {
274 return []Login{}, ErrNoProfileStore
275 }
276 return c.profiles.listLogins(profile, num, offset)
277 }
279 // GetToken returns the Token specified from the tokenStore associated with the Context.
280 // If refresh is true, the token input should be compared against the refresh tokens, not the
281 // access tokens.
282 func (c Context) GetToken(token string, refresh bool) (Token, error) {
283 if c.tokens == nil {
284 return Token{}, ErrNoTokenStore
285 }
286 return c.tokens.getToken(token, refresh)
287 }
289 // SaveToken stores the passed Token in the tokenStore associated with the Context.
290 func (c Context) SaveToken(token Token) error {
291 if c.tokens == nil {
292 return ErrNoTokenStore
293 }
294 return c.tokens.saveToken(token)
295 }
297 // RemoveToken removes the Token identified by the passed token string from the tokenStore associated
298 // with the Context.
299 func (c Context) RemoveToken(token string) error {
300 if c.tokens == nil {
301 return ErrNoTokenStore
302 }
303 return c.tokens.removeToken(token)
304 }
306 // RevokeToken revokes the Token identfied by the passed token string from the tokenStore associated
307 // with the context. If refresh is true, the token input should be compared against the refresh tokens,
308 // not the access tokens.
309 func (c Context) RevokeToken(token string, refresh bool) error {
310 if c.tokens == nil {
311 return ErrNoTokenStore
312 }
313 return c.tokens.revokeToken(token, refresh)
314 }
316 // GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
317 // profileID from the tokenStore associated with the Context, skipping offset Tokens.
318 func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
319 if c.tokens == nil {
320 return []Token{}, ErrNoTokenStore
321 }
322 return c.tokens.getTokensByProfileID(profileID, num, offset)
323 }
325 // CreateSession stores the passed Session in the sessionStore associated with the Context.
326 func (c Context) CreateSession(session Session) error {
327 if c.sessions == nil {
328 return ErrNoSessionStore
329 }
330 return c.sessions.createSession(session)
331 }
333 // GetSession returns the Session specified from the sessionStore associated with the Context.
334 func (c Context) GetSession(id string) (Session, error) {
335 if c.sessions == nil {
336 return Session{}, ErrNoSessionStore
337 }
338 return c.sessions.getSession(id)
339 }
341 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
342 // the Context.
343 func (c Context) RemoveSession(id string) error {
344 if c.sessions == nil {
345 return ErrNoSessionStore
346 }
347 return c.sessions.removeSession(id)
348 }
350 // ListSessions returns a slice of up to num Sessions from the sessionStore associated with the Context,
351 // ordered by the date they were created, descending. If before.IsZero() returns false, only Sessions
352 // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
353 // that Profile will be returned.
354 func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
355 if c.sessions != nil {
356 return []Session{}, ErrNoSessionStore
357 }
358 return c.sessions.listSessions(profile, before, num)
359 }