diff options
Diffstat (limited to 'src/gracha.go')
-rw-r--r-- | src/gracha.go | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/src/gracha.go b/src/gracha.go new file mode 100644 index 0000000..dcff98d --- /dev/null +++ b/src/gracha.go @@ -0,0 +1,1011 @@ +package gracha + +import ( + "crypto/rand" + "database/sql" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "time" + + "guuid" + "liteq" + "scrypt" + g "gobang" +) + + + +type tablesT struct{ + users string + userChanges string + tokens string + roles string + roleChanges string + sessions string + attempts string + audit string +} + +type queriesT struct{ + userByEmail func(string) (userT, error) + userByToken func([]byte) (userT, error) + register func(string, []byte, []byte) (userT, error) + confirm func([]byte) (sessionT, error) + login func(string, string) (sessionT, error) + refresh func([]byte) (sessionT, error) + resetPassword func(int64, []byte, []byte) (sessionT, error) + resetAndConfirm func(int64, []byte, []byte) (sessionT, error) + changePassword func(int64, []byte) (sessionT, error) + sessionByUUID func([]byte) (sessionT, error) + logout func([]byte) error + logoutOthers func([]byte) error + logoutAll func([]byte) error + close func() error +} + +type confirmationT struct{ +} + +type userT struct{ + id int64 + timestr string + timestamp time.Time + uuid []byte + email string + username *string + salt []byte + pwhash []byte + // confirmation + confirmed_at *time.Time + metadatastr *string + metadata *map[string]interface{} +} + +type sessionT struct{ + id int64 + timestr string + timestamp time.Time + uuid guuid.UUID + user_id int64 + type_ string + revokedstr *string + revoked_at *time.Time + metadatastr *string + metadata *map[string]interface{} +} + +type Auth struct{ + tables tablesT + queries queriesT + q liteq.Queue +} + +type consumerT struct{ + topic string + handlerFn func(Auth) func([]byte) error +} + + + +const ( + NEW_USER = "new-user" + SEND_CONFIRMATION_REQUEST = "send-confirmation-request" + FORGOT_PASSWORD_REQUEST = "forgot-password-request" + + defaultPrefix = "gracha" + + day = 24 * time.Hour +) + +var ( + SessionDuration = 7 * day + RegisterTimeout = 15 * time.Second + + ErrPasswordMismatch = errors.New("gracha: password and its confirmation don't match") + ErrPasswordTooShort = errors.New("gracha: bad username/passphrase combo") + ErrRegisterTimeout = errors.New("gracha: timeout when creating user") + ErrAlreadyRegistered = errors.New("gracha: user already registered") + ErrEmailPasswordCombo = errors.New("gracha: bad username/passphrase combo") + ErrUnconfirmedUser = errors.New("gracha: user email is not confirmed") + ErrRevokedSession = errors.New("gracha: this session was revoked") + ErrSessionExpired = errors.New("gracha: session expired") +) + + + +func tablesFrom(prefix string) (tablesT, error) { + if !g.ValidSQLTablePrefix(prefix) { + return tablesT{}, g.ErrBadSQLTablePrefix + } + + users := prefix + "-users" + userChanges := prefix + "-user-changes" + tokens := prefix + "-tokens" + roles := prefix + "-roles" + roleChanges := prefix + "-role-changes" + sessions := prefix + "-sessions" + attempts := prefix + "-attempts" + audit := prefix + "-audit" + return tablesT{ + users: users, + userChanges: userChanges, + tokens: tokens, + roles: roles, + roleChanges: roleChanges, + sessions: sessions, + attempts: attempts, + audit: audit, + }, nil +} + +func createTables(db *sql.DB, tables tablesT) error { + const tmpl = ` + BEGIN TRANSACTION; + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + uuid BLOB NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + username TEXT UNIQUE, + salt BLOB NOT NULL UNIQUE, + pwhash BLOB NOT NULL, + confirmed_at TEXT, + confirmer_id INTEGER REFERENCES "%s"(id), + metadata TEXT + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + user_id INTEGER NOT NULL REFERENCES "%s"(id), + attribute TEXT NOT NULL, + value TEXT NOT NULL, + op BOOLEAN NOT NULL + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + uuid BLOB NOT NULL UNIQUE, + type TEXT NOT NULL + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL REFERENCES "%s"(id), + role TEXT NOT NULL, + UNIQUE (user_id, role) + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + user_id INTEGER NOT NULL REFERENCES "%s"(id), + role TEXT NOT NULL, + op BOOLEAN NOT NULL + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + uuid BLOB NOT NULL UNIQUE, + user_id INTEGER NOT NULL REFERENCES "%s"(id), + type TEXT NOT NULL, + revoked_at TEXT, + revoker_id INTEGER REFERENCES "%s"(id), + metadata TEXT + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + user_id INTEGER REFERENCES "%s"(id), + session_id INTEGER REFERENCES "%s"(id), + metadata TEXT + ); + CREATE TABLE IF NOT EXISTS "%s" ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp TEXT NOT NULL DEFAULT (%s), + uuid BLOB NOT NULL UNIQUE, + attribute TEXT NOT NULL, + value TEXT NOT NULL, + op BOOLEAN NOT NULL, + metadata TEXT + ); + COMMIT TRANSACTION; + ` + sql := fmt.Sprintf( + tmpl, + tables.users, + g.SQLiteNow, + tables.tokens, + tables.userChanges, + g.SQLiteNow, + tables.users, + tables.tokens, + g.SQLiteNow, + tables.roles, + tables.users, + tables.roleChanges, + g.SQLiteNow, + tables.users, + tables.sessions, + g.SQLiteNow, + tables.users, + tables.sessions, + tables.attempts, + g.SQLiteNow, + tables.users, + tables.sessions, + tables.audit, + g.SQLiteNow, + ) + /// fmt.Println(sql) /// + + _, err := db.Exec(sql) + return err +} + +func userByEmailQuery( + db *sql.DB, + tables tablesT, +) (func(string) (userT, error), func() error, error) { + const tmpl = ` + SELECT id, timestamp, uuid, email, username, pwhash, metadata + FROM "%s" WHERE email = ?; + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(email string) (userT, error) { + var user userT + err := stmt.QueryRow(email).Scan(&user.id) // FIXME: build user + return user, err + } + + return fn, stmt.Close, nil +} + +func userByTokenQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) (userT, error), func() error, error) { + const tmpl = ` + SELECT id, timestamp, uuid, email, username, pwhash, metadata + FROM "%s" WHERE email = ?; + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(token []byte) (userT, error) { + var user userT + err := stmt.QueryRow(token).Scan(&user.id) // FIXME: build user + return user, err + } + + return fn, stmt.Close, nil +} + +func registerQuery( + db *sql.DB, + tables tablesT, +) (func(string, []byte, []byte) (userT, error), func() error, error) { + const tmpl = ` + INSERT INTO "%s" (uuid, email, username, salt, pwhash, metadata) + VALUES (?, ?, ?, ?, ?, ?); + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(email string, salt []byte, pwhash []byte) (userT, error) { + /* + timestamp TEXT NOT NULL DEFAULT (%s), + uuid BLOB NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + username TEXT UNIQUE, + pwhash TEXT NOT NULL, + metadata TEXT + */ + + var user userT + // err := stmt.QueryRow( + ret, err := stmt.Exec( + guuid.NewBytes(), + email, + "credentials.username", + salt, + pwhash, + "credentials.metadata", + // ).Scan(&user.email) + // FIXME: finish + ) + if false { + fmt.Printf("ret: %#v\n", ret) + fmt.Printf("user: %#v\n", user) + } + return user, err + } + + return fn, stmt.Close, nil +} + +func loginQuery( + db *sql.DB, + tables tablesT, +) (func(string, string) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT INTO "%s" (t3, t4) VALUES (?, ?); + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(email string, pwhash string) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(email, pwhash).Scan(session) + // FIXME: finish + return session, err + } + + return fn, stmt.Close, nil +} + +func refreshQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(uuid []byte) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(uuid).Scan(&session) + return session, err + } + + return fn, stmt.Close, nil +} + +func resetPasswordQuery( + db *sql.DB, + tables tablesT, +) (func(int64, []byte, []byte) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(id int64, pwhash []byte, token []byte) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(id, pwhash, token).Scan(&session) + return session, err + } + + return fn, stmt.Close, nil +} + +func resetAndConfirmQuery( + db *sql.DB, + tables tablesT, +) (func(int64, []byte, []byte) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(id int64, pwhash []byte, token []byte) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(id, pwhash, token).Scan(&session) + return session, err + } + + return fn, stmt.Close, nil +} + +func changePasswordQuery( + db *sql.DB, + tables tablesT, +) (func(int64, []byte) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(id int64, pwhash []byte) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(id, pwhash).Scan(&session) + return session, err + } + + return fn, stmt.Close, nil +} + +func sessionByUUIDQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) (sessionT, error), func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(uuid []byte) (sessionT, error) { + var session sessionT + err := stmt.QueryRow(uuid).Scan(&session) + return session, err + } + + return fn, stmt.Close, nil +} + +func logoutQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) error, func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(uuid []byte) error { + _, err := stmt.Exec(uuid) + return err + } + + return fn, stmt.Close, nil +} + +func logoutOthersQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) error, func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(uuid []byte) error { + _, err := stmt.Exec(uuid) + return err + } + + return fn, stmt.Close, nil +} + +func logoutAllQuery( + db *sql.DB, + tables tablesT, +) (func([]byte) error, func() error, error) { + const tmpl = ` + -- INSERT SOMETHING %s + ` + sql := fmt.Sprintf(tmpl, tables.users) + /// fmt.Println(sql) /// + + stmt, err := db.Prepare(sql) + if err != nil { + return nil, nil, err + } + + fn := func(uuid []byte) error { + _, err := stmt.Exec(uuid) + return err + } + + return fn, stmt.Close, nil +} + +func initDB(db *sql.DB, tables tablesT) (queriesT, error) { + createTablesErr := createTables(db, tables) + userByEmail, userByEmailClose, userByEmailErr := userByEmailQuery(db, tables) + userByToken, userByTokenClose, userByTokenErr := userByTokenQuery(db, tables) + register, registerClose, registerErr := registerQuery(db, tables) + login, loginClose, loginErr := loginQuery(db, tables) + refresh, refreshClose, refreshErr := refreshQuery(db, tables) + resetPassword, resetPasswordClose, resetPasswordErr := resetPasswordQuery(db, tables) + resetAndConfirm, resetAndConfirmClose, resetAndConfirmErr := resetAndConfirmQuery(db, tables) + changePassword, changePasswordClose, changePasswordErr := changePasswordQuery(db, tables) + sessionByUUID, sessionByUUIDClose, sessionByUUIDErr := sessionByUUIDQuery(db, tables) + logout, logoutClose, logoutErr := logoutQuery(db, tables) + logoutOthers, logoutOthersClose, logoutOthersErr := logoutOthersQuery(db, tables) + logoutAll, logoutAllClose, logoutAllErr := logoutAllQuery(db, tables) + + errs := []error { + createTablesErr, + userByEmailErr, + userByTokenErr, + registerErr, + loginErr, + refreshErr, + resetPasswordErr, + resetAndConfirmErr, + changePasswordErr, + sessionByUUIDErr, + logoutErr, + logoutOthersErr, + logoutAllErr, + } + err := g.SomeError(errs) + if err != nil { + return queriesT{}, err + } + + close := func() error { + fns := [](func() error){ + userByEmailClose, + userByTokenClose, + registerClose, + loginClose, + refreshClose, + resetPasswordClose, + resetAndConfirmClose, + changePasswordClose, + sessionByUUIDClose, + logoutClose, + logoutOthersClose, + logoutAllClose, + } + return g.SomeFnError(fns) + } + + return queriesT{ + userByEmail: userByEmail, + userByToken: userByToken, + register: register, + login: login, + close: close, + refresh: refresh, + resetPassword: resetPassword, + resetAndConfirm: resetAndConfirm, + changePassword: changePassword, + sessionByUUID: sessionByUUID, + logout: logout, + logoutOthers: logoutOthers, + logoutAll: logoutAll, + }, nil +} + +func newUserPayload(email string, salt []byte, pwhash []byte) ([]byte, error) { + data := make(map[string]interface{}) + data["email"] = email + data["salt"] = hex.EncodeToString(salt) + data["pwhash"] = hex.EncodeToString(pwhash) + return json.Marshal(data) +} + +func publishRegister( + q liteq.Queue, + email string, + salt []byte, + pwhash []byte, + flowId []byte, +) error { + payload, err := newUserPayload(email, salt, pwhash) + if err != nil { + return err + } + + return q.Publish(NEW_USER, payload, flowId) +} + +func register( + auth Auth, + email string, + salt []byte, + pwhash []byte, +) (userT, error) { + flowId := guuid.NewBytes() + waiter := auth.q.WaitFor(NEW_USER, flowId) + defer auth.q.Unwait(waiter) + + err := publishRegister(auth.q, email, salt, pwhash, flowId) + if err != nil { + return userT{}, err + } + + select { + case <-time.After(RegisterTimeout): + return userT{}, ErrRegisterTimeout + case <-waiter: + return auth.queries.userByEmail(email) + } +} + +func (auth Auth) Register( + email string, + password string, + confirmPassword string, +) (userT, error) { + if password != confirmPassword { + return userT{}, ErrPasswordMismatch + } + + if len(password) < scrypt.MinimumPasswordLength { + return userT{}, ErrPasswordTooShort + } + + // special check for sql.ErrNoRows to combat enumeration attacks. + _, lookupErr := auth.queries.userByEmail(email) + if lookupErr != nil && lookupErr != sql.ErrNoRows { + return userT{}, lookupErr + } + + salt, err := scrypt.SaltFrom(rand.Reader) + if err != nil { + return userT{}, err + } + + pwhash, err := scrypt.HashFrom([]byte(password), salt) + if err != nil { + return userT{}, err + } + + /* + We also try to register anyway, to prevent disk IO timing attacks. + */ + user, err := register(auth, email, salt, pwhash) + if err != nil { + if lookupErr != nil { + return userT{}, ErrAlreadyRegistered + } + return userT{}, err + } + + return user, nil +} + +func sendConfirmationPayload(email string) ([]byte, error) { + data := make(map[string]interface{}) + data["email"] = email + return json.Marshal(data) +} + +func publishSendConfirmation(q liteq.Queue, email string, flowId []byte) error { + payload, err := sendConfirmationPayload(email) + if err != nil { + return err + } + + return q.Publish(SEND_CONFIRMATION_REQUEST, payload, flowId) +} + +func (auth Auth) ResendConfirmation(email string) error { + return publishSendConfirmation(auth.q, email, guuid.NewBytes()) +} + +func (auth Auth) ConfirmEmail(token guuid.UUID) (sessionT, error) { + return auth.queries.confirm(token[:]) +} + +func (auth Auth) LoginEmail( + email string, + password string, +) (sessionT, error) { + if len(password) < scrypt.MinimumPasswordLength { + return sessionT{}, ErrPasswordTooShort + } + + // special check for sql.ErrNoRows to combat enumeration attacks. + user, err := auth.queries.userByEmail(email) + if err != nil && err != sql.ErrNoRows { + return sessionT{}, err + } + + ok, err := scrypt.CheckFrom([]byte(password), user.salt, user.pwhash) + if err != nil { + return sessionT{}, err + } + if !ok { + return sessionT{}, ErrEmailPasswordCombo + } + + if user.confirmed_at == nil { + return sessionT{}, ErrUnconfirmedUser + } + + dbSession, err := auth.queries.login(email, password) + if err != nil { + return sessionT{}, err + } + + return dbSession, nil +} + +func forgotPasswordPayload(email string) ([]byte, error) { + data := make(map[string]interface{}) + data["email"] = email + return json.Marshal(data) +} + +func publishForgotPassword(q liteq.Queue, email string, flowId []byte) error { + payload, err := forgotPasswordPayload(email) + if err != nil { + return err + } + + return q.Publish(FORGOT_PASSWORD_REQUEST, payload, flowId) +} + +func (auth Auth) ForgotPassword(email string) error { + // special check for sql.ErrNoRows to combat enumeration attacks. + user, err := auth.queries.userByEmail(email) + if err != nil && err != sql.ErrNoRows { + return err + } + + return publishForgotPassword(auth.q, user.email, guuid.NewBytes()) +} + +func checkSession(session sessionT, now time.Time) error { + if session.revoked_at != nil { + return ErrRevokedSession + } + + if session.timestamp.Add(SessionDuration).After(now) { + return ErrSessionExpired + } + + return nil +} + +func validateSession(lookupFn func([]byte) (sessionT, error), session sessionT) error { + dbSession, err := lookupFn(session.uuid[:]) + if err != nil { + return err + } + + return checkSession(dbSession, time.Now()) +} + +func (auth Auth) Refresh(session sessionT) (sessionT, error) { + err := validateSession(auth.queries.sessionByUUID, session) + if err != nil { + return sessionT{}, err + } + + return auth.queries.refresh(session.uuid[:]) +} + +func (auth Auth) ResetPassword( + token []byte, + password string, + confirmPassword string, +) (sessionT, error) { + if password != confirmPassword { + return sessionT{}, ErrPasswordMismatch + } + + if len(password) < scrypt.MinimumPasswordLength { + return sessionT{}, ErrPasswordTooShort + } + + user, err := auth.queries.userByToken(token) + if err != nil { + return sessionT{}, err + } + + pwhash, err := scrypt.HashFrom([]byte(password), user.salt) + if err != nil { + return sessionT{}, err + } + + if user.confirmed_at != nil { + return auth.queries.resetPassword(user.id, pwhash, token) + } else { + return auth.queries.resetAndConfirm(user.id, pwhash, token) + } +} + +func (auth Auth) ChangePassword( + user userT, + currentPassword string, + newPassword string, + confirmNewPassword string, +) (sessionT, error) { + if newPassword != confirmNewPassword { + return sessionT{}, ErrPasswordMismatch + } + + if len(newPassword) < scrypt.MinimumPasswordLength { + return sessionT{}, ErrPasswordTooShort + } + + pwhash, err := scrypt.HashFrom([]byte(newPassword), user.salt) + if err != nil { + return sessionT{}, err + } + + if user.confirmed_at == nil { + return sessionT{}, ErrUnconfirmedUser + } + + return auth.queries.changePassword(user.id, pwhash) +} + +func runLogout( + lookupFn func([]byte) (sessionT, error), + session sessionT, + queryFn func([]byte) error, +) error { + err := validateSession(lookupFn, session) + if err != nil { + return err + } + + return queryFn(session.uuid[:]) +} + +func (auth Auth) Logout(session sessionT) error { + return runLogout(auth.queries.sessionByUUID, session, auth.queries.logout) +} + +func (auth Auth) LogoutOthers(session sessionT) error { + return runLogout(auth.queries.sessionByUUID, session, auth.queries.logoutOthers) +} + +func (auth Auth) LogoutAll(session sessionT) error { + return runLogout(auth.queries.sessionByUUID, session, auth.queries.logoutAll) +} + +func (auth Auth) Close() error { + return auth.queries.close() +} + +func newUserHandler(auth Auth) func([]byte) error { + return func(payload []byte) error { + return nil + } +} + +func sendConfirmationRequestHandler(auth Auth) func([]byte) error { + return func(payload []byte) error { + return nil + } +} + +func forgotPasswordRequestHandler(auth Auth) func([]byte) error { + return func(payload []byte) error { + return nil + } +} + +var consumers = []consumerT{ + consumerT{ + topic: NEW_USER, + handlerFn: newUserHandler, + }, + consumerT{ + topic: SEND_CONFIRMATION_REQUEST, + handlerFn: sendConfirmationRequestHandler, + }, + consumerT{ + topic: FORGOT_PASSWORD_REQUEST, + handlerFn: forgotPasswordRequestHandler, + }, +} +func registerConsumers(auth Auth, consumers []consumerT) { + for _, consumer := range consumers { + auth.q.Subscribe( + consumer.topic, + defaultPrefix + "-" + consumer.topic, + consumer.handlerFn(auth), + ) + } +} + +func NewWithPrefix(db *sql.DB, q liteq.Queue, prefix string) (Auth, error) { + tables, err := tablesFrom(prefix) + if err != nil { + return Auth{}, err + } + + queries, err := initDB(db, tables) + if err != nil { + return Auth{}, err + } + + return Auth{ + tables: tables, + queries: queries, + q: q, + }, nil +} + +func New(db *sql.DB, q liteq.Queue) (Auth, error) { + return NewWithPrefix(db, q, defaultPrefix) +} + + + +func Main() { + g.Init() + q := new(liteq.Queue) + sql.Register("sqlite-liteq", liteq.MakeDriver(q)) + + db, err := sql.Open("sqlite-liteq", "file:gracha.db?mode=memory&cache=shared") + if err != nil { + panic(err) + } + // defer db.Close() + + *q, err = liteq.New(db) + if err != nil { + panic(err) + } + // defer q.Close() + + auth, err := New(db, *q) + if err != nil { + fmt.Println(err) + panic(err) + } + + user, err := auth.Register("contact@example.com", "password", "password") + if false { + fmt.Printf("user: %#v\n", user) + fmt.Printf("err: %#v\n", err) + } + + return + fmt.Printf("q: %#v\n", q) + fmt.Printf("auth: %#v\n", auth) +} |