auth
auth/context.go
Use an environment variable to set the JWT secret. When setting up the authd server, populate the JWT secret using a JWT_SECRET environment variable. Incidentally, we also included the subscriptions scope, for testing purposes while creating code.secondbit.org/ducky/subscriptions. We now also log the port we're listening on, listen on all interfaces (instead of just 127.0.0.1), and changed the port to 9000 instead of 8080.
1 package auth
3 import (
4 "html/template"
5 "io"
6 "log"
7 "net/url"
8 "time"
10 "code.secondbit.org/uuid.hg"
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 scopes scopeStore
25 loginVerificationNotifier loginVerificationNotifier
26 config Config
27 }
29 // NewContext takes a Config instance and uses it to bootstrap a Context
30 // using the information provided in the Config variable.
31 func NewContext(config Config) (Context, error) {
32 if config.iterations == 0 {
33 return Context{}, ErrConfigNotInitialized
34 }
35 context := Context{
36 clients: config.ClientStore,
37 authCodes: config.AuthCodeStore,
38 profiles: config.ProfileStore,
39 tokens: config.TokenStore,
40 sessions: config.SessionStore,
41 scopes: config.ScopeStore,
42 loginVerificationNotifier: config.LoginVerificationNotifier,
43 template: config.Template,
44 config: config,
45 }
46 var err error
47 context.loginURI, err = url.Parse(config.LoginURI)
48 if err != nil {
49 log.Println(err)
50 return Context{}, ErrInvalidLoginURI
51 }
52 return context, nil
53 }
55 // Render uses the HTML templates associated with the Context to render the
56 // template specified by name to out using data to fill any template variables.
57 func (c Context) Render(out io.Writer, name string, data interface{}) {
58 if c.template == nil {
59 log.Println("No template set on Context, can't render anything!")
60 return
61 }
62 err := c.template.ExecuteTemplate(out, name, data)
63 if err != nil {
64 log.Println("Error executing template", name, ":", err)
65 }
66 }
68 // GetClient returns a single Client by its ID from the
69 // clientStore associated with the Context.
70 func (c Context) GetClient(id uuid.ID) (Client, error) {
71 if c.clients == nil {
72 return Client{}, ErrNoClientStore
73 }
74 return c.clients.getClient(id)
75 }
77 // SaveClient stores the passed Client in the clientStore
78 // associated with the Context.
79 func (c Context) SaveClient(client Client) error {
80 if c.clients == nil {
81 return ErrNoClientStore
82 }
83 return c.clients.saveClient(client)
84 }
86 // UpdateClient applies the specified ClientChange to the Client
87 // with the specified ID in the clientStore associated with the
88 // Context.
89 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
90 if c.clients == nil {
91 return ErrNoClientStore
92 }
93 return c.clients.updateClient(id, change)
94 }
96 // ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
97 // that have the specified OwnerID in the clientStore associated with the Context.
98 func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
99 if c.clients == nil {
100 return []Client{}, ErrNoClientStore
101 }
102 return c.clients.listClientsByOwner(ownerID, num, offset)
103 }
105 func (c Context) DeleteClientsByOwner(ownerID uuid.ID) error {
106 if c.clients == nil {
107 return ErrNoClientStore
108 }
109 return c.clients.deleteClientsByOwner(ownerID)
110 }
112 // AddEndpoints stores the specified Endpoints in the clientStore associated with the Context.
113 func (c Context) AddEndpoints(endpoints []Endpoint) error {
114 if c.clients == nil {
115 return ErrNoClientStore
116 }
117 for pos, endpoint := range endpoints {
118 u, err := normalizeURIString(endpoint.URI)
119 if err != nil {
120 return err
121 }
122 endpoint.NormalizedURI = u
123 endpoints[pos] = endpoint
124 }
125 return c.clients.addEndpoints(endpoints)
126 }
128 // GetEndpoint retrieves the Endpoint with the specified ID from the clientStore associated
129 // with the Context, if and only if it belongs to the Client with the specified ID.
130 func (c Context) GetEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
131 if c.clients == nil {
132 return Endpoint{}, ErrNoClientStore
133 }
134 return c.clients.getEndpoint(client, endpoint)
135 }
137 // RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
138 // with the Context, and disassociates the Endpoint from the specified Client.
139 func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
140 if c.clients == nil {
141 return ErrNoClientStore
142 }
143 return c.clients.removeEndpoint(client, endpoint)
144 }
146 // CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
147 // to the Client specified by the passed ID and match the URI passed. URI matches must be
148 // performed according to RFC 3986 Section 6.
149 func (c Context) CheckEndpoint(client uuid.ID, URI string) (bool, error) {
150 if c.clients == nil {
151 return false, ErrNoClientStore
152 }
153 u, err := normalizeURIString(URI)
154 if err != nil {
155 return false, err
156 }
157 return c.clients.checkEndpoint(client, u)
158 }
160 // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
161 // to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
162 // exclusive.
163 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
164 if c.clients == nil {
165 return []Endpoint{}, ErrNoClientStore
166 }
167 return c.clients.listEndpoints(client, num, offset)
168 }
170 func (c Context) RemoveEndpointsByClientID(client uuid.ID) error {
171 if c.clients == nil {
172 return ErrNoClientStore
173 }
174 return c.clients.removeEndpointsByClientID(client)
175 }
177 // CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
178 // passed ID in the clientStore associated with the Context.
179 func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
180 if c.clients == nil {
181 return 0, ErrNoClientStore
182 }
183 return c.clients.countEndpoints(client)
184 }
186 // GetAuthorizationCode returns the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with the
187 // Context.
188 func (c Context) GetAuthorizationCode(code string) (AuthorizationCode, error) {
189 if c.authCodes == nil {
190 return AuthorizationCode{}, ErrNoAuthorizationCodeStore
191 }
192 return c.authCodes.getAuthorizationCode(code)
193 }
195 // SaveAuthorizationCode stores the passed AuthorizationCode in the authorizationCodeStore associated with the Context.
196 func (c Context) SaveAuthorizationCode(authCode AuthorizationCode) error {
197 if c.authCodes == nil {
198 return ErrNoAuthorizationCodeStore
199 }
200 return c.authCodes.saveAuthorizationCode(authCode)
201 }
203 // DeleteAuthorizationCode removes the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with
204 // the Context.
205 func (c Context) DeleteAuthorizationCode(code string) error {
206 if c.authCodes == nil {
207 return ErrNoAuthorizationCodeStore
208 }
209 return c.authCodes.deleteAuthorizationCode(code)
210 }
212 // DeleteAuthorizationCodesByProfileID removes the AuthorizationCodes associated with the Profile specified by the provided ID from the
213 // authorizationCodeStore associated with the Context.
214 func (c Context) DeleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
215 if c.authCodes == nil {
216 return ErrNoAuthorizationCodeStore
217 }
218 return c.authCodes.deleteAuthorizationCodesByProfileID(profileID)
219 }
221 func (c Context) DeleteAuthorizationCodesByClientID(clientID uuid.ID) error {
222 if c.authCodes == nil {
223 return ErrNoAuthorizationCodeStore
224 }
225 return c.authCodes.deleteAuthorizationCodesByClientID(clientID)
226 }
228 // UseAuthorizationCode marks the AuthorizationCode specified by the provided code as used in the authorizationCodeStore associated with
229 // the Context. Once an AuthorizationCode is marked as used, its Used property will be set to true when retrieved from the authorizationCodeStore.
230 func (c Context) UseAuthorizationCode(code string) error {
231 if c.authCodes == nil {
232 return ErrNoAuthorizationCodeStore
233 }
234 return c.authCodes.useAuthorizationCode(code)
235 }
237 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
238 // the Context.
239 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
240 if c.profiles == nil {
241 return Profile{}, ErrNoProfileStore
242 }
243 return c.profiles.getProfileByID(id)
244 }
246 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
247 // with the Context.
248 func (c Context) GetProfileByLogin(value string) (Profile, error) {
249 if c.profiles == nil {
250 return Profile{}, ErrNoProfileStore
251 }
252 return c.profiles.getProfileByLogin(value)
253 }
255 // SaveProfile inserts the passed Profile into the profileStore associated with the Context.
256 func (c Context) SaveProfile(profile Profile) error {
257 if c.profiles == nil {
258 return ErrNoProfileStore
259 }
260 return c.profiles.saveProfile(profile)
261 }
263 // UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
264 // in the profileStore associated with the Context.
265 func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
266 if c.profiles == nil {
267 return ErrNoProfileStore
268 }
269 return c.profiles.updateProfile(id, change)
270 }
272 // UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
273 // specified IDs in the profileStore associated with the Context.
274 func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
275 if c.profiles == nil {
276 return ErrNoProfileStore
277 }
278 return c.profiles.updateProfiles(ids, change)
279 }
281 // DeleteProfile removes the specified Profile from the profileStore associated with the Context.
282 func (c Context) DeleteProfile(id uuid.ID) error {
283 if c.profiles == nil {
284 return ErrNoProfileStore
285 }
286 return c.profiles.deleteProfile(id)
287 }
289 // AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
290 // the newly-created Login with the Orofile in login.ProfileID.
291 func (c Context) AddLogin(login Login) error {
292 if c.profiles == nil {
293 return ErrNoProfileStore
294 }
295 return c.profiles.addLogin(login)
296 }
298 // GetLogin returns the Login specified from the profileStore associated with the Context.
299 func (c Context) GetLogin(value string) (Login, error) {
300 if c.profiles == nil {
301 return Login{}, ErrNoProfileStore
302 }
303 return c.profiles.getLogin(value)
304 }
306 // RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
307 // the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
308 // deleted Login from the Profile in login.ProfileID.
309 func (c Context) RemoveLogin(value string, profile uuid.ID) error {
310 if c.profiles == nil {
311 return ErrNoProfileStore
312 }
313 return c.profiles.removeLogin(value, profile)
314 }
316 // RemoveLoginsByProfile removes all Logins connected to the specified Profile in the profileStore
317 // associated with the Context and disassociates them from the Profile.
318 func (c Context) RemoveLoginsByProfile(profile uuid.ID) error {
319 if c.profiles == nil {
320 return ErrNoProfileStore
321 }
322 return c.profiles.removeLoginsByProfile(profile)
323 }
325 // RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
326 // the Context to the value passed in as when.
327 func (c Context) RecordLoginUse(value string, when time.Time) error {
328 if c.profiles == nil {
329 return ErrNoProfileStore
330 }
331 return c.profiles.recordLoginUse(value, when)
332 }
334 // VerifyLogin sets the Verified property of the Login specied to true in the profileStore associated with
335 // the Context, assuming the Verification property of the Login in the profileStore matches the verification
336 // passed. If the verifications do not match, an ErrLoginVerificationInvalid error is returned.
337 func (c Context) VerifyLogin(value, verification string) error {
338 if c.profiles == nil {
339 return ErrNoProfileStore
340 }
341 return c.profiles.verifyLogin(value, verification)
342 }
344 // ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
345 // associated with the Context, skipping offset Profiles.
346 func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
347 if c.profiles == nil {
348 return []Login{}, ErrNoProfileStore
349 }
350 return c.profiles.listLogins(profile, num, offset)
351 }
353 // GetToken returns the Token specified from the tokenStore associated with the Context.
354 // If refresh is true, the token input should be compared against the refresh tokens, not the
355 // access tokens.
356 func (c Context) GetToken(token string, refresh bool) (Token, error) {
357 if c.tokens == nil {
358 return Token{}, ErrNoTokenStore
359 }
360 return c.tokens.getToken(token, refresh)
361 }
363 // SaveToken stores the passed Token in the tokenStore associated with the Context.
364 func (c Context) SaveToken(token Token) error {
365 if c.tokens == nil {
366 return ErrNoTokenStore
367 }
368 return c.tokens.saveToken(token)
369 }
371 // RevokeToken revokes the Token identfied by the passed token string from the tokenStore associated
372 // with the context.
373 func (c Context) RevokeToken(token string) error {
374 if c.tokens == nil {
375 return ErrNoTokenStore
376 }
377 return c.tokens.revokeToken(token)
378 }
380 // RevokeTokensByProfileID revokes the Tokens associated with the Profile identified by the passed ID in
381 // the tokenStore associated with the Context.
382 func (c Context) RevokeTokensByProfileID(profileID uuid.ID) error {
383 if c.tokens == nil {
384 return ErrNoTokenStore
385 }
386 return c.tokens.revokeTokensByProfileID(profileID)
387 }
389 func (c Context) RevokeTokensByClientID(clientID uuid.ID) error {
390 if c.tokens == nil {
391 return ErrNoTokenStore
392 }
393 return c.tokens.revokeTokensByClientID(clientID)
394 }
396 // GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
397 // profileID from the tokenStore associated with the Context, skipping offset Tokens.
398 func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
399 if c.tokens == nil {
400 return []Token{}, ErrNoTokenStore
401 }
402 return c.tokens.getTokensByProfileID(profileID, num, offset)
403 }
405 // CreateSession stores the passed Session in the sessionStore associated with the Context.
406 func (c Context) CreateSession(session Session) error {
407 if c.sessions == nil {
408 return ErrNoSessionStore
409 }
410 return c.sessions.createSession(session)
411 }
413 // GetSession returns the Session specified from the sessionStore associated with the Context.
414 func (c Context) GetSession(id string) (Session, error) {
415 if c.sessions == nil {
416 return Session{}, ErrNoSessionStore
417 }
418 return c.sessions.getSession(id)
419 }
421 // TerminateSession sets the Session identified by the passed ID as inactive in the sessionStore assocated
422 // with the Context.
423 func (c Context) TerminateSession(id string) error {
424 if c.sessions == nil {
425 return ErrNoSessionStore
426 }
427 return c.sessions.terminateSession(id)
428 }
430 // TerminateSessionsByProfile sets the Sessions associated with the passed Profile ID as inactive in the
431 // sessionStore associated with the Context.
432 func (c Context) TerminateSessionsByProfile(profile uuid.ID) error {
433 if c.sessions == nil {
434 return ErrNoSessionStore
435 }
436 return c.sessions.terminateSessionsByProfile(profile)
437 }
439 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
440 // the Context.
441 func (c Context) RemoveSession(id string) error {
442 if c.sessions == nil {
443 return ErrNoSessionStore
444 }
445 return c.sessions.removeSession(id)
446 }
448 // ListSessions returns a slice of up to num Sessions from the sessionStore associated with the Context,
449 // ordered by the date they were created, descending. If before.IsZero() returns false, only Sessions
450 // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
451 // that Profile will be returned.
452 func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
453 if c.sessions == nil {
454 return []Session{}, ErrNoSessionStore
455 }
456 return c.sessions.listSessions(profile, before, num)
457 }
459 func (c Context) CreateScopes(scopes []Scope) error {
460 if c.scopes == nil {
461 return ErrNoScopeStore
462 }
463 return c.scopes.createScopes(scopes)
464 }
466 func (c Context) GetScopes(ids []string) ([]Scope, error) {
467 if c.scopes == nil {
468 return []Scope{}, ErrNoScopeStore
469 }
470 return c.scopes.getScopes(ids)
471 }
473 func (c Context) UpdateScope(id string, change ScopeChange) error {
474 if c.scopes == nil {
475 return ErrNoScopeStore
476 }
477 return c.scopes.updateScope(id, change)
478 }
480 func (c Context) RemoveScopes(ids []string) error {
481 if c.scopes == nil {
482 return ErrNoScopeStore
483 }
484 return c.scopes.removeScopes(ids)
485 }
487 func (c Context) ListScopes() ([]Scope, error) {
488 if c.scopes == nil {
489 return []Scope{}, ErrNoScopeStore
490 }
491 return c.scopes.listScopes()
492 }
494 func (c Context) SendLoginVerification(login Login) {
495 if c.loginVerificationNotifier == nil {
496 log.Println("Login verification notifier not set!")
497 }
498 c.loginVerificationNotifier.SendLoginVerification(login)
499 }