Compare commits
9 Commits
dev
...
5caf3f6b62
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5caf3f6b62 | ||
|
|
b18a171ac2 | ||
|
|
37e1c1a616 | ||
|
|
6a01fe84ac | ||
|
|
7adde54a41 | ||
|
|
1eb8781550 | ||
|
|
352e3ee6c5 | ||
|
|
1930eb37fb | ||
|
|
912fc99e9e |
BIN
b0esche_cloud/assets/icons/b0esche-cloud-icon-sharp.png
Normal file
BIN
b0esche_cloud/assets/icons/b0esche-cloud-icon-sharp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 574 KiB |
BIN
b0esche_cloud/assets/icons/b0esche-cloud-icon.png
Normal file
BIN
b0esche_cloud/assets/icons/b0esche-cloud-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 731 KiB |
@@ -1,6 +1,7 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import '../session/session_bloc.dart';
|
||||
import '../session/session_event.dart';
|
||||
import '../session/session_state.dart';
|
||||
import 'auth_event.dart';
|
||||
import 'auth_state.dart';
|
||||
import '../../services/api_client.dart';
|
||||
@@ -252,7 +253,22 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
CheckAuthRequested event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
// Check if token is valid in SessionBloc
|
||||
emit(AuthUnauthenticated());
|
||||
// Check if session is active from persistent storage
|
||||
final sessionState = sessionBloc.state;
|
||||
|
||||
if (sessionState is SessionActive) {
|
||||
// Session already active - emit authenticated state with minimal info
|
||||
// The full user info will be fetched when needed
|
||||
emit(
|
||||
AuthAuthenticated(
|
||||
token: sessionState.token,
|
||||
userId: '',
|
||||
username: '',
|
||||
email: '',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(AuthUnauthenticated());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,98 @@
|
||||
import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'session_event.dart';
|
||||
import 'session_state.dart';
|
||||
|
||||
class SessionBloc extends Bloc<SessionEvent, SessionState> {
|
||||
Timer? _expiryTimer;
|
||||
static const String _tokenKey = 'auth_token';
|
||||
static const String _expiryKey = 'auth_expiry';
|
||||
|
||||
SessionBloc() : super(SessionInitial()) {
|
||||
on<SessionStarted>(_onSessionStarted);
|
||||
on<SessionExpired>(_onSessionExpired);
|
||||
on<SessionRefreshed>(_onSessionRefreshed);
|
||||
on<SessionEnded>(_onSessionEnded);
|
||||
on<SessionRestored>(_onSessionRestored);
|
||||
}
|
||||
|
||||
void _onSessionStarted(SessionStarted event, Emitter<SessionState> emit) {
|
||||
void _onSessionStarted(SessionStarted event, Emitter<SessionState> emit) async {
|
||||
final expiresAt = DateTime.now().add(
|
||||
const Duration(minutes: 15),
|
||||
); // Match Go
|
||||
|
||||
// Save token to persistent storage
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_tokenKey, event.token);
|
||||
await prefs.setString(_expiryKey, expiresAt.toIso8601String());
|
||||
|
||||
emit(SessionActive(token: event.token, expiresAt: expiresAt));
|
||||
_startExpiryTimer(expiresAt);
|
||||
}
|
||||
|
||||
void _onSessionExpired(SessionExpired event, Emitter<SessionState> emit) {
|
||||
_expiryTimer?.cancel();
|
||||
_clearStoredSession();
|
||||
emit(SessionExpiredState());
|
||||
}
|
||||
|
||||
void _onSessionRefreshed(SessionRefreshed event, Emitter<SessionState> emit) {
|
||||
void _onSessionRefreshed(SessionRefreshed event, Emitter<SessionState> emit) async {
|
||||
final expiresAt = DateTime.now().add(const Duration(minutes: 15));
|
||||
|
||||
// Update stored token
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_tokenKey, event.newToken);
|
||||
await prefs.setString(_expiryKey, expiresAt.toIso8601String());
|
||||
|
||||
emit(SessionActive(token: event.newToken, expiresAt: expiresAt));
|
||||
_startExpiryTimer(expiresAt);
|
||||
}
|
||||
|
||||
void _onSessionEnded(SessionEnded event, Emitter<SessionState> emit) {
|
||||
_expiryTimer?.cancel();
|
||||
_clearStoredSession();
|
||||
emit(SessionInitial());
|
||||
}
|
||||
|
||||
void _onSessionRestored(SessionRestored event, Emitter<SessionState> emit) {
|
||||
final expiresAt = event.expiresAt;
|
||||
final now = DateTime.now();
|
||||
|
||||
// Check if token is still valid
|
||||
if (expiresAt.isAfter(now)) {
|
||||
emit(SessionActive(token: event.token, expiresAt: expiresAt));
|
||||
_startExpiryTimer(expiresAt);
|
||||
} else {
|
||||
// Token expired, clear it
|
||||
_clearStoredSession();
|
||||
emit(SessionInitial());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearStoredSession() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_tokenKey);
|
||||
await prefs.remove(_expiryKey);
|
||||
}
|
||||
|
||||
static Future<void> restoreSession(SessionBloc bloc) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final token = prefs.getString(_tokenKey);
|
||||
final expiryStr = prefs.getString(_expiryKey);
|
||||
|
||||
if (token != null && expiryStr != null) {
|
||||
try {
|
||||
final expiresAt = DateTime.parse(expiryStr);
|
||||
bloc.add(SessionRestored(token: token, expiresAt: expiresAt));
|
||||
} catch (e) {
|
||||
// Invalid stored data, clear it
|
||||
await prefs.remove(_tokenKey);
|
||||
await prefs.remove(_expiryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _startExpiryTimer(DateTime expiresAt) {
|
||||
_expiryTimer?.cancel();
|
||||
final duration = expiresAt.difference(DateTime.now());
|
||||
|
||||
@@ -28,3 +28,13 @@ class SessionRefreshed extends SessionEvent {
|
||||
}
|
||||
|
||||
class SessionEnded extends SessionEvent {}
|
||||
|
||||
class SessionRestored extends SessionEvent {
|
||||
final String token;
|
||||
final DateTime expiresAt;
|
||||
|
||||
const SessionRestored({required this.token, required this.expiresAt});
|
||||
|
||||
@override
|
||||
List<Object> get props => [token, expiresAt];
|
||||
}
|
||||
|
||||
@@ -41,23 +41,37 @@ void main() {
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
class MainApp extends StatefulWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
State<MainApp> createState() => _MainAppState();
|
||||
}
|
||||
|
||||
class _MainAppState extends State<MainApp> {
|
||||
final _sessionBloc = SessionBloc();
|
||||
late final AuthBloc _authBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_authBloc = AuthBloc(
|
||||
apiClient: ApiClient(_sessionBloc),
|
||||
sessionBloc: _sessionBloc,
|
||||
);
|
||||
// Restore session from persistent storage
|
||||
SessionBloc.restoreSession(_sessionBloc);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<SessionBloc>(create: (_) => SessionBloc()),
|
||||
BlocProvider<AuthBloc>(
|
||||
create: (context) => AuthBloc(
|
||||
apiClient: ApiClient(context.read<SessionBloc>()),
|
||||
sessionBloc: context.read<SessionBloc>(),
|
||||
),
|
||||
),
|
||||
BlocProvider<SessionBloc>.value(value: _sessionBloc),
|
||||
BlocProvider<AuthBloc>.value(value: _authBloc),
|
||||
BlocProvider<ActivityBloc>(
|
||||
create: (context) =>
|
||||
ActivityBloc(ActivityApi(ApiClient(context.read<SessionBloc>()))),
|
||||
ActivityBloc(ActivityApi(ApiClient(_sessionBloc))),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
@@ -66,4 +80,11 @@ class MainApp extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_authBloc.close();
|
||||
_sessionBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +190,16 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDrive(OrganizationState state) {
|
||||
return state is OrganizationLoaded && state.selectedOrg != null
|
||||
? FileExplorer(orgId: state.selectedOrg!.id)
|
||||
: const FileExplorer(orgId: 'org1');
|
||||
Widget _buildDrive(OrganizationState state, AuthState authState) {
|
||||
if (state is OrganizationLoaded && state.selectedOrg != null) {
|
||||
// Show selected organization's files
|
||||
return FileExplorer(orgId: state.selectedOrg!.id);
|
||||
} else if (authState is AuthAuthenticated) {
|
||||
// Show personal workspace using user's email as workspace ID
|
||||
return FileExplorer(orgId: authState.email);
|
||||
} else {
|
||||
return const FileExplorer(orgId: 'personal');
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildNavButton(String label, IconData icon, {bool isAvatar = false}) {
|
||||
@@ -313,6 +319,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
Expanded(
|
||||
child: _buildDrive(
|
||||
orgState,
|
||||
state,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
32
go_cloud/Dockerfile
Normal file
32
go_cloud/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
# ---------- Build stage ----------
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install ca-certs for HTTPS / OIDC
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
# Cache dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Build statically linked binary
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||
go build -o backend ./cmd/api
|
||||
|
||||
# ---------- Runtime stage ----------
|
||||
FROM gcr.io/distroless/base-debian12
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/backend /app/backend
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
USER nonroot:nonroot
|
||||
|
||||
ENTRYPOINT ["/app/backend"]
|
||||
@@ -1,23 +1,22 @@
|
||||
module go.b0esche.cloud/backend
|
||||
|
||||
go 1.25.5
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.6 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@@ -19,10 +21,13 @@ github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
@@ -33,3 +38,5 @@ golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -27,6 +27,7 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.CORS)
|
||||
r.Use(middleware.RateLimit)
|
||||
|
||||
// Health check
|
||||
@@ -59,49 +60,51 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
||||
})
|
||||
})
|
||||
|
||||
// Auth middleware for protected routes
|
||||
r.Use(middleware.Auth(jwtManager, db))
|
||||
// Protected routes (with auth middleware)
|
||||
r.Route("/", func(r chi.Router) {
|
||||
r.Use(middleware.Auth(jwtManager, db))
|
||||
|
||||
// Org routes
|
||||
r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
||||
listOrgsHandler(w, req, db, jwtManager)
|
||||
})
|
||||
r.Post("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
||||
createOrgHandler(w, req, db, auditLogger, jwtManager)
|
||||
})
|
||||
// Org routes
|
||||
r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
||||
listOrgsHandler(w, req, db, jwtManager)
|
||||
})
|
||||
r.Post("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
||||
createOrgHandler(w, req, db, auditLogger, jwtManager)
|
||||
})
|
||||
|
||||
// Org-scoped routes
|
||||
r.Route("/orgs/{orgId}", func(r chi.Router) {
|
||||
r.Use(middleware.Org(db, auditLogger))
|
||||
// Org-scoped routes
|
||||
r.Route("/orgs/{orgId}", func(r chi.Router) {
|
||||
r.Use(middleware.Org(db, auditLogger))
|
||||
|
||||
// File routes
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileRead)).Get("/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
listFilesHandler(w, req)
|
||||
})
|
||||
r.Route("/files/{fileId}", func(r chi.Router) {
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) {
|
||||
viewerHandler(w, req, db, auditLogger)
|
||||
// File routes
|
||||
r.With(middleware.Permission(db, auditLogger, permission.FileRead)).Get("/files", func(w http.ResponseWriter, req *http.Request) {
|
||||
listFilesHandler(w, req)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentEdit)).Get("/edit", func(w http.ResponseWriter, req *http.Request) {
|
||||
editorHandler(w, req, db, auditLogger)
|
||||
r.Route("/files/{fileId}", func(r chi.Router) {
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) {
|
||||
viewerHandler(w, req, db, auditLogger)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentEdit)).Get("/edit", func(w http.ResponseWriter, req *http.Request) {
|
||||
editorHandler(w, req, db, auditLogger)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentEdit)).Post("/annotations", func(w http.ResponseWriter, req *http.Request) {
|
||||
annotationsHandler(w, req, db, auditLogger)
|
||||
})
|
||||
r.Get("/meta", func(w http.ResponseWriter, req *http.Request) {
|
||||
fileMetaHandler(w, req)
|
||||
})
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentEdit)).Post("/annotations", func(w http.ResponseWriter, req *http.Request) {
|
||||
annotationsHandler(w, req, db, auditLogger)
|
||||
r.Get("/activity", func(w http.ResponseWriter, req *http.Request) {
|
||||
activityHandler(w, req, db)
|
||||
})
|
||||
r.Get("/meta", func(w http.ResponseWriter, req *http.Request) {
|
||||
fileMetaHandler(w, req)
|
||||
r.With(middleware.Permission(db, auditLogger, permission.OrgManage)).Get("/members", func(w http.ResponseWriter, req *http.Request) {
|
||||
listMembersHandler(w, req, db)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.OrgManage)).Patch("/members/{userId}", func(w http.ResponseWriter, req *http.Request) {
|
||||
updateMemberRoleHandler(w, req, db, auditLogger)
|
||||
})
|
||||
})
|
||||
r.Get("/activity", func(w http.ResponseWriter, req *http.Request) {
|
||||
activityHandler(w, req, db)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.OrgManage)).Get("/members", func(w http.ResponseWriter, req *http.Request) {
|
||||
listMembersHandler(w, req, db)
|
||||
})
|
||||
r.With(middleware.Permission(db, auditLogger, permission.OrgManage)).Patch("/members/{userId}", func(w http.ResponseWriter, req *http.Request) {
|
||||
updateMemberRoleHandler(w, req, db, auditLogger)
|
||||
})
|
||||
})
|
||||
}) // Close protected routes
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -21,6 +21,23 @@ var RequestID = middleware.RequestID
|
||||
var Logger = middleware.Logger
|
||||
var Recoverer = middleware.Recoverer
|
||||
|
||||
// CORS middleware
|
||||
func CORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Implement rate limiter
|
||||
var RateLimit = func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user