forked from mystiq/dex
fd20b213bb
Dex's Postgres client currently uses the `timestamp` datatype for storing times. This lops of timezones with no conversion, causing times to lose locality information. We could convert all times to UTC before storing them, but this is a backward incompatible change for upgrades, since the new version of dex would still be reading times from the database with no locality. Because of this intrinsic issue that current Postgres users don't save any timezone data, we chose to treat any existing installation as corrupted and change the datatype used for times to `timestamptz`. This is a breaking change, but it seems hard to offer an alternative that's both correct and backward compatible. Additionally, an internal flag has been added to SQL flavors, `supportsTimezones`. This allows us to handle SQLite3, which doesn't support timezones, while still storing timezones in other flavors. Flavors that don't support timezones are explicitly converted to UTC.
158 lines
3.7 KiB
Go
158 lines
3.7 KiB
Go
package sql
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
func (c *conn) migrate() (int, error) {
|
|
_, err := c.Exec(`
|
|
create table if not exists migrations (
|
|
num integer not null,
|
|
at timestamptz not null
|
|
);
|
|
`)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("creating migration table: %v", err)
|
|
}
|
|
|
|
i := 0
|
|
done := false
|
|
for {
|
|
err := c.ExecTx(func(tx *trans) error {
|
|
// Within a transaction, perform a single migration.
|
|
var (
|
|
num sql.NullInt64
|
|
n int
|
|
)
|
|
if err := tx.QueryRow(`select max(num) from migrations;`).Scan(&num); err != nil {
|
|
return fmt.Errorf("select max migration: %v", err)
|
|
}
|
|
if num.Valid {
|
|
n = int(num.Int64)
|
|
}
|
|
if n >= len(migrations) {
|
|
done = true
|
|
return nil
|
|
}
|
|
|
|
migrationNum := n + 1
|
|
m := migrations[n]
|
|
if _, err := tx.Exec(m.stmt); err != nil {
|
|
return fmt.Errorf("migration %d failed: %v", migrationNum, err)
|
|
}
|
|
|
|
q := `insert into migrations (num, at) values ($1, now());`
|
|
if _, err := tx.Exec(q, migrationNum); err != nil {
|
|
return fmt.Errorf("update migration table: %v", err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return i, err
|
|
}
|
|
if done {
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
|
|
return i, nil
|
|
}
|
|
|
|
type migration struct {
|
|
stmt string
|
|
// TODO(ericchiang): consider adding additional fields like "forDrivers"
|
|
}
|
|
|
|
// All SQL flavors share migration strategies.
|
|
var migrations = []migration{
|
|
{
|
|
stmt: `
|
|
create table client (
|
|
id text not null primary key,
|
|
secret text not null,
|
|
redirect_uris bytea not null, -- JSON array of strings
|
|
trusted_peers bytea not null, -- JSON array of strings
|
|
public boolean not null,
|
|
name text not null,
|
|
logo_url text not null
|
|
);
|
|
|
|
create table auth_request (
|
|
id text not null primary key,
|
|
client_id text not null,
|
|
response_types bytea not null, -- JSON array of strings
|
|
scopes bytea not null, -- JSON array of strings
|
|
redirect_uri text not null,
|
|
nonce text not null,
|
|
state text not null,
|
|
force_approval_prompt boolean not null,
|
|
|
|
logged_in boolean not null,
|
|
|
|
claims_user_id text not null,
|
|
claims_username text not null,
|
|
claims_email text not null,
|
|
claims_email_verified boolean not null,
|
|
claims_groups bytea not null, -- JSON array of strings
|
|
|
|
connector_id text not null,
|
|
connector_data bytea,
|
|
|
|
expiry timestamptz not null
|
|
);
|
|
|
|
create table auth_code (
|
|
id text not null primary key,
|
|
client_id text not null,
|
|
scopes bytea not null, -- JSON array of strings
|
|
nonce text not null,
|
|
redirect_uri text not null,
|
|
|
|
claims_user_id text not null,
|
|
claims_username text not null,
|
|
claims_email text not null,
|
|
claims_email_verified boolean not null,
|
|
claims_groups bytea not null, -- JSON array of strings
|
|
|
|
connector_id text not null,
|
|
connector_data bytea,
|
|
|
|
expiry timestamptz not null
|
|
);
|
|
|
|
create table refresh_token (
|
|
id text not null primary key,
|
|
client_id text not null,
|
|
scopes bytea not null, -- JSON array of strings
|
|
nonce text not null,
|
|
|
|
claims_user_id text not null,
|
|
claims_username text not null,
|
|
claims_email text not null,
|
|
claims_email_verified boolean not null,
|
|
claims_groups bytea not null, -- JSON array of strings
|
|
|
|
connector_id text not null,
|
|
connector_data bytea
|
|
);
|
|
|
|
create table password (
|
|
email text not null primary key,
|
|
hash bytea not null,
|
|
username text not null,
|
|
user_id text not null
|
|
);
|
|
|
|
-- keys is a weird table because we only ever expect there to be a single row
|
|
create table keys (
|
|
id text not null primary key,
|
|
verification_keys bytea not null, -- JSON array
|
|
signing_key bytea not null, -- JSON object
|
|
signing_key_pub bytea not null, -- JSON object
|
|
next_rotation timestamptz not null
|
|
);
|
|
`,
|
|
},
|
|
}
|