Add an endpoint to validate and register profiles.
Add a newProfileRequest object that defines the user-specified properties of a
new Profile.
Add a helper that validates a newProfileRequest and modifies it for
sanitization, mostly just removing leading and trailing whitespace.
Add MaxNameLength, MaxUsernameLength, and MaxEmailLength constants to hold the
maximum length for those properties.
Add errors to be returned when a users attempts to log in with a profile that is
compromised or locked.
Add the bare bones of a CreateProfileHandler that validates a profile
registration request adn uses it to create a Profile and at least one Login.
Create a requestError struct that is used for returning API errors, along with
constants for the slugs we'll use to signal those errors.
10 "code.secondbit.org/uuid"
13 // Context wraps the different storage interfaces and should
14 // be used as the main point of interaction for the data storage
17 template *template.Template
20 authCodes authorizationCodeStore
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) {
31 clients: config.ClientStore,
32 authCodes: config.AuthCodeStore,
33 profiles: config.ProfileStore,
34 tokens: config.TokenStore,
35 sessions: config.SessionStore,
36 template: config.Template,
40 context.loginURI, err = url.Parse(config.LoginURI)
43 return Context{}, ErrInvalidLoginURI
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!")
55 err := c.template.ExecuteTemplate(out, name, data)
57 log.Println("Error executing template", name, ":", err)
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) {
65 return Client{}, ErrNoClientStore
67 return c.clients.getClient(id)
70 // SaveClient stores the passed Client in the clientStore
71 // associated with the Context.
72 func (c Context) SaveClient(client Client) error {
74 return ErrNoClientStore
76 return c.clients.saveClient(client)
79 // UpdateClient applies the specified ClientChange to the Client
80 // with the specified ID in the clientStore associated with the
82 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
84 return ErrNoClientStore
86 return c.clients.updateClient(id, change)
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 {
93 return ErrNoClientStore
95 return c.clients.deleteClient(id)
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
104 return c.clients.listClientsByOwner(ownerID, num, offset)
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
113 return c.clients.addEndpoint(client, endpoint)
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
122 return c.clients.removeEndpoint(client, endpoint)
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
132 return c.clients.checkEndpoint(client, URI)
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,
138 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
139 if c.clients == nil {
140 return []Endpoint{}, ErrNoClientStore
142 return c.clients.listEndpoints(client, num, offset)
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
151 return c.clients.countEndpoints(client)
154 // GetAuthorizationCode returns the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with the
156 func (c Context) GetAuthorizationCode(code string) (AuthorizationCode, error) {
157 if c.authCodes == nil {
158 return AuthorizationCode{}, ErrNoAuthorizationCodeStore
160 return c.authCodes.getAuthorizationCode(code)
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
168 return c.authCodes.saveAuthorizationCode(authCode)
171 // DeleteAuthorizationCode removes the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with
173 func (c Context) DeleteAuthorizationCode(code string) error {
174 if c.authCodes == nil {
175 return ErrNoAuthorizationCodeStore
177 return c.authCodes.deleteAuthorizationCode(code)
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
186 return c.authCodes.useAuthorizationCode(code)
189 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
191 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
192 if c.profiles == nil {
193 return Profile{}, ErrNoProfileStore
195 return c.profiles.getProfileByID(id)
198 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
200 func (c Context) GetProfileByLogin(value string) (Profile, error) {
201 if c.profiles == nil {
202 return Profile{}, ErrNoProfileStore
204 return c.profiles.getProfileByLogin(value)
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
212 return c.profiles.saveProfile(profile)
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
221 return c.profiles.updateProfile(id, change)
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
230 return c.profiles.updateProfiles(ids, change)
233 // DeleteProfile removes the Profile specified by the passed ID from the profileStore associated
235 func (c Context) DeleteProfile(id uuid.ID) error {
236 if c.profiles == nil {
237 return ErrNoProfileStore
239 return c.profiles.deleteProfile(id)
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
248 return c.profiles.addLogin(login)
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
258 return c.profiles.removeLogin(value, profile)
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
267 return c.profiles.recordLoginUse(value, when)
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
276 return c.profiles.listLogins(profile, num, offset)
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
282 func (c Context) GetToken(token string, refresh bool) (Token, error) {
284 return Token{}, ErrNoTokenStore
286 return c.tokens.getToken(token, refresh)
289 // SaveToken stores the passed Token in the tokenStore associated with the Context.
290 func (c Context) SaveToken(token Token) error {
292 return ErrNoTokenStore
294 return c.tokens.saveToken(token)
297 // RemoveToken removes the Token identified by the passed token string from the tokenStore associated
299 func (c Context) RemoveToken(token string) error {
301 return ErrNoTokenStore
303 return c.tokens.removeToken(token)
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 {
311 return ErrNoTokenStore
313 return c.tokens.revokeToken(token, refresh)
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) {
320 return []Token{}, ErrNoTokenStore
322 return c.tokens.getTokensByProfileID(profileID, num, offset)
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
330 return c.sessions.createSession(session)
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
338 return c.sessions.getSession(id)
341 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
343 func (c Context) RemoveSession(id string) error {
344 if c.sessions == nil {
345 return ErrNoSessionStore
347 return c.sessions.removeSession(id)
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
358 return c.sessions.listSessions(profile, before, num)